Add sources for API 34
https://dl.google.com/android/repository/sources-34_r01.zip
Test: None
Change-Id: I254306ce746dcadecd8f756a445c667d8fecbd2a
diff --git a/android-34/android/os/AggregateBatteryConsumer.java b/android-34/android/os/AggregateBatteryConsumer.java
new file mode 100644
index 0000000..7a153ef
--- /dev/null
+++ b/android-34/android/os/AggregateBatteryConsumer.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.proto.ProtoOutputStream;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Contains power consumption data across the entire device.
+ *
+ * {@hide}
+ */
+public final class AggregateBatteryConsumer extends BatteryConsumer {
+ static final int CONSUMER_TYPE_AGGREGATE = 0;
+
+ static final int COLUMN_INDEX_SCOPE = BatteryConsumer.COLUMN_COUNT;
+ static final int COLUMN_INDEX_CONSUMED_POWER = COLUMN_INDEX_SCOPE + 1;
+ static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 2;
+
+ AggregateBatteryConsumer(BatteryConsumerData data) {
+ super(data);
+ }
+
+ private AggregateBatteryConsumer(@NonNull Builder builder) {
+ super(builder.mData, builder.mPowerComponentsBuilder.build());
+ }
+
+ int getScope() {
+ return mData.getInt(COLUMN_INDEX_SCOPE);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, boolean skipEmptyComponents) {
+ mPowerComponents.dump(pw, skipEmptyComponents);
+ }
+
+ @Override
+ public double getConsumedPower() {
+ return mData.getDouble(COLUMN_INDEX_CONSUMED_POWER);
+ }
+
+ /** Serializes this object to XML */
+ void writeToXml(TypedXmlSerializer serializer,
+ @BatteryUsageStats.AggregateBatteryConsumerScope int scope) throws IOException {
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_AGGREGATE);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE, scope);
+ serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, getConsumedPower());
+ mPowerComponents.writeToXml(serializer);
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_AGGREGATE);
+ }
+
+ /** Parses an XML representation and populates the BatteryUsageStats builder */
+ static void parseXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder)
+ throws XmlPullParserException, IOException {
+ final int scope = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE);
+ final Builder consumerBuilder = builder.getAggregateBatteryConsumerBuilder(scope);
+
+ int eventType = parser.getEventType();
+ if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
+ BatteryUsageStats.XML_TAG_AGGREGATE)) {
+ throw new XmlPullParserException("Invalid XML parser state");
+ }
+
+ consumerBuilder.setConsumedPower(
+ parser.getAttributeDouble(null, BatteryUsageStats.XML_ATTR_POWER));
+
+ while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
+ BatteryUsageStats.XML_TAG_AGGREGATE))
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
+ PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder);
+ }
+ }
+ eventType = parser.next();
+ }
+ }
+
+ void writePowerComponentModelProto(@NonNull ProtoOutputStream proto) {
+ for (int i = 0; i < POWER_COMPONENT_COUNT; i++) {
+ final int powerModel = getPowerModel(i);
+ if (powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) continue;
+
+ final long token = proto.start(BatteryUsageStatsAtomsProto.COMPONENT_MODELS);
+ proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.COMPONENT, i);
+ proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_MODEL,
+ powerModelToProtoEnum(powerModel));
+ proto.end(token);
+ }
+ }
+
+ /**
+ * Builder for DeviceBatteryConsumer.
+ */
+ public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> {
+ public Builder(BatteryConsumer.BatteryConsumerData data, int scope) {
+ super(data, CONSUMER_TYPE_AGGREGATE);
+ data.putInt(COLUMN_INDEX_SCOPE, scope);
+ }
+
+ /**
+ * Sets the total power included in this aggregate.
+ */
+ public Builder setConsumedPower(double consumedPowerMah) {
+ mData.putDouble(COLUMN_INDEX_CONSUMED_POWER, consumedPowerMah);
+ return this;
+ }
+
+ /**
+ * Adds power and usage duration from the supplied AggregateBatteryConsumer.
+ */
+ public void add(AggregateBatteryConsumer aggregateBatteryConsumer) {
+ setConsumedPower(mData.getDouble(COLUMN_INDEX_CONSUMED_POWER)
+ + aggregateBatteryConsumer.getConsumedPower());
+ mPowerComponentsBuilder.addPowerAndDuration(aggregateBatteryConsumer.mPowerComponents);
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public AggregateBatteryConsumer build() {
+ return new AggregateBatteryConsumer(this);
+ }
+ }
+}
diff --git a/android-34/android/os/AppZygote.java b/android-34/android/os/AppZygote.java
new file mode 100644
index 0000000..07fbe4a
--- /dev/null
+++ b/android-34/android/os/AppZygote.java
@@ -0,0 +1,138 @@
+/*
+ * 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.content.pm.ProcessInfo;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.Zygote;
+
+import dalvik.system.VMRuntime;
+
+/**
+ * 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;
+ private final ProcessInfo mProcessInfo;
+
+ public AppZygote(ApplicationInfo appInfo, ProcessInfo processInfo, int zygoteUid, int uidGidMin,
+ int uidGidMax) {
+ mAppInfo = appInfo;
+ mProcessInfo = processInfo;
+ 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) {
+ mZygote.close();
+ // use killProcessGroup() here, so we kill all untracked children as well.
+ Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+ mZygote = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void connectToZygoteIfNeededLocked() {
+ String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi :
+ Build.SUPPORTED_ABIS[0];
+ try {
+ int runtimeFlags = Zygote.getMemorySafetyRuntimeFlagsForSecondaryZygote(
+ mAppInfo, mProcessInfo);
+ mZygote = Process.ZYGOTE_PROCESS.startChildZygote(
+ "com.android.internal.os.AppZygoteInit",
+ mAppInfo.processName + "_zygote",
+ mZygoteUid,
+ mZygoteUid,
+ null, // gids
+ runtimeFlags,
+ "app_zygote", // seInfo
+ abi, // abi
+ abi, // acceptedAbiList
+ VMRuntime.getInstructionSet(abi), // 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-34/android/os/ArtModuleServiceManager.java b/android-34/android/os/ArtModuleServiceManager.java
new file mode 100644
index 0000000..0009e61
--- /dev/null
+++ b/android-34/android/os/ArtModuleServiceManager.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the ART
+ * mainline module.
+ *
+ * Only the ART mainline module will be able to access an instance of this class.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class ArtModuleServiceManager {
+ /** @hide */
+ public ArtModuleServiceManager() {}
+
+ /** A class that exposes the method to obtain each system service. */
+ public static final class ServiceRegisterer {
+ @NonNull private final String mServiceName;
+
+ /** @hide */
+ public ServiceRegisterer(@NonNull String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Returns the service from the service manager.
+ *
+ * If the service is not running, servicemanager will attempt to start it, and this function
+ * will wait for it to be ready.
+ *
+ * @return {@code null} only if there are permission problems or fatal errors.
+ */
+ @Nullable
+ public IBinder waitForService() {
+ return ServiceManager.waitForService(mServiceName);
+ }
+ }
+
+ /** Returns {@link ServiceRegisterer} for the "artd" service. */
+ @NonNull
+ public ServiceRegisterer getArtdServiceRegisterer() {
+ return new ServiceRegisterer("artd");
+ }
+}
diff --git a/android-34/android/os/AsyncResult.java b/android-34/android/os/AsyncResult.java
new file mode 100644
index 0000000..e80528b
--- /dev/null
+++ b/android-34/android/os/AsyncResult.java
@@ -0,0 +1,74 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+/** @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-34/android/os/AsyncTask.java b/android-34/android/os/AsyncTask.java
new file mode 100644
index 0000000..e1dabd3
--- /dev/null
+++ b/android-34/android/os/AsyncTask.java
@@ -0,0 +1,811 @@
+/*
+ * 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.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
+
+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 was intended to enable proper and easy use of the UI thread. However, the most
+ * common use case was for integrating into UI, and that would cause Context leaks, missed
+ * callbacks, or crashes on configuration changes. It also has inconsistent behavior on different
+ * versions of the platform, swallows exceptions from {@code doInBackground}, and does not provide
+ * much utility over using {@link Executor}s directly.</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>
+ *
+ * @deprecated Use the standard <code>java.util.concurrent</code> or
+ * <a href="https://developer.android.com/topic/libraries/architecture/coroutines">
+ * Kotlin concurrency utilities</a> instead.
+ */
+@Deprecated
+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.
+ *
+ * @deprecated Using a single thread pool for a general purpose results in suboptimal behavior
+ * for different tasks. Small, CPU-bound tasks benefit from a bounded pool and queueing, and
+ * long-running blocking tasks, such as network operations, benefit from many threads. Use or
+ * create an {@link Executor} configured for your use case.
+ */
+ @Deprecated
+ 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.
+ *
+ * @deprecated Globally serializing tasks results in excessive queuing for unrelated operations.
+ */
+ @Deprecated
+ 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-34/android/os/BadParcelableException.java b/android-34/android/os/BadParcelableException.java
new file mode 100644
index 0000000..9b1343c
--- /dev/null
+++ b/android-34/android/os/BadParcelableException.java
@@ -0,0 +1,39 @@
+/*
+ * 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);
+ }
+ /** @hide */
+ public BadParcelableException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/android-34/android/os/BadTypeParcelableException.java b/android-34/android/os/BadTypeParcelableException.java
new file mode 100644
index 0000000..2ca3bd2
--- /dev/null
+++ b/android-34/android/os/BadTypeParcelableException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** Used by Parcel to signal that the type on the payload was not expected by the caller. */
+class BadTypeParcelableException extends BadParcelableException {
+ BadTypeParcelableException(String msg) {
+ super(msg);
+ }
+ BadTypeParcelableException(Exception cause) {
+ super(cause);
+ }
+ BadTypeParcelableException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/android-34/android/os/BaseBundle.java b/android-34/android/os/BaseBundle.java
new file mode 100644
index 0000000..4e3adfb
--- /dev/null
+++ b/android-34/android/os/BaseBundle.java
@@ -0,0 +1,1958 @@
+/*
+ * 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 java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.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.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * 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 {
+ /** @hide */
+ protected static final String TAG = "Bundle";
+ static final boolean DEBUG = false;
+
+ /**
+ * Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ 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", see {@link #setShouldDefuse(boolean)}
+ * for more details.
+ * <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. Also:
+ * <ul>
+ * <li>If it was the deserialization of a custom item (eg. {@link Parcelable}) that caused the
+ * exception, {@code null} will be returned but the item will be held in the map in its
+ * serialized form (lazy value).
+ * <li>If the exception happened during partial deserialization, that is, during the read of
+ * the map and its basic types (while skipping custom types), the map will be left empty.
+ * </ul>
+ *
+ * @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 will be set to null.
+ */
+ @UnsupportedAppUsage
+ volatile Parcel mParcelledData = null;
+
+ /**
+ * Whether {@link #mParcelledData} was generated by native code or not.
+ */
+ private boolean mParcelledByNative;
+
+ /**
+ * Flag indicating if mParcelledData is only referenced in this bundle.
+ * mParcelledData could be referenced elsewhere if mMap contains lazy values,
+ * and bundle data is copied to another bundle using putAll or the copy constructors.
+ */
+ boolean mOwnsLazyValues = true;
+
+ /** Tracks how many lazy values are referenced in mMap */
+ private int mLazyValues = 0;
+
+ /**
+ * As mParcelledData is set to null when it is unparcelled, we keep a weak reference to
+ * it to aid in recycling it. Do not use this reference otherwise.
+ * Is non-null iff mMap contains lazy values.
+ */
+ private WeakReference<Parcel> mWeakParcelledData = null;
+
+ /**
+ * 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) {
+ this(b, /* deep */ false);
+ }
+
+ /**
+ * Constructs a {@link BaseBundle} containing a copy of {@code from}.
+ *
+ * @param from The bundle to be copied.
+ * @param deep Whether is a deep or shallow copy.
+ *
+ * @hide
+ */
+ BaseBundle(BaseBundle from, boolean deep) {
+ synchronized (from) {
+ mClassLoader = from.mClassLoader;
+
+ if (from.mMap != null) {
+ mOwnsLazyValues = false;
+ from.mOwnsLazyValues = false;
+
+ 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;
+ }
+
+ final Parcel parcelledData;
+ if (from.mParcelledData != null) {
+ if (from.isEmptyParcel()) {
+ parcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
+ mParcelledByNative = false;
+ } else {
+ parcelledData = Parcel.obtain();
+ parcelledData.appendFrom(from.mParcelledData, 0,
+ from.mParcelledData.dataSize());
+ parcelledData.setDataPosition(0);
+ mParcelledByNative = from.mParcelledByNative;
+ }
+ } else {
+ parcelledData = null;
+ mParcelledByNative = false;
+ }
+
+ // Keep as last statement to ensure visibility of other fields
+ mParcelledData = parcelledData;
+ }
+ }
+
+ /**
+ * 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;
+ }
+ try {
+ return getValueAt(0, String.class);
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning("getPairValue()", "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
+ final void unparcel() {
+ unparcel(/* itemwise */ false);
+ }
+
+ /** Deserializes the underlying data and each item if {@code itemwise} is true. */
+ final void unparcel(boolean itemwise) {
+ synchronized (this) {
+ final Parcel source = mParcelledData;
+ if (source != null) {
+ Preconditions.checkState(mOwnsLazyValues);
+ initializeFromParcelLocked(source, /*ownsParcel*/ true, mParcelledByNative);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "unparcel "
+ + Integer.toHexString(System.identityHashCode(this))
+ + ": no parcelled data");
+ }
+ }
+ if (itemwise) {
+ if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
+ Slog.wtf(TAG,
+ "Attempting to unparcel all items in a Bundle while in transit; this "
+ + "may remove elements intended for the final desitination.",
+ new Throwable());
+ }
+ for (int i = 0, n = mMap.size(); i < n; i++) {
+ // Triggers deserialization of i-th item, if needed
+ getValueAt(i, /* clazz */ null);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the value for key {@code key}.
+ *
+ * This call should always be made after {@link #unparcel()} or inside a lock after making sure
+ * {@code mMap} is not null.
+ *
+ * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in
+ * other deprecated APIs.
+ *
+ * @hide
+ */
+ @Deprecated
+ @Nullable
+ final Object getValue(String key) {
+ return getValue(key, /* clazz */ null);
+ }
+
+ /** Same as {@link #getValue(String, Class, Class[])} with no item types. */
+ @Nullable
+ final <T> T getValue(String key, @Nullable Class<T> clazz) {
+ // Avoids allocating Class[0] array
+ return getValue(key, clazz, (Class<?>[]) null);
+ }
+
+ /**
+ * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code
+ * null} for no type check).
+ *
+ * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
+ *
+ * This call should always be made after {@link #unparcel()} or inside a lock after making sure
+ * {@code mMap} is not null.
+ *
+ * @hide
+ */
+ @Nullable
+ final <T> T getValue(String key, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
+ int i = mMap.indexOfKey(key);
+ return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null;
+ }
+
+ /**
+ * Returns the value for a certain position in the array map for expected return type {@code
+ * clazz} (or pass {@code null} for no type check).
+ *
+ * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
+ *
+ * This call should always be made after {@link #unparcel()} or inside a lock after making sure
+ * {@code mMap} is not null.
+ *
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ final <T> T getValueAt(int i, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
+ Object object = mMap.valueAt(i);
+ if (object instanceof BiFunction<?, ?, ?>) {
+ synchronized (this) {
+ object = unwrapLazyValueFromMapLocked(i, clazz, itemTypes);
+ }
+ }
+ return (clazz != null) ? clazz.cast(object) : (T) object;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @GuardedBy("this")
+ private Object unwrapLazyValueFromMapLocked(int i, @Nullable Class<?> clazz,
+ @Nullable Class<?>... itemTypes) {
+ Object object = mMap.valueAt(i);
+ if (object instanceof BiFunction<?, ?, ?>) {
+ try {
+ object = ((BiFunction<Class<?>, Class<?>[], ?>) object).apply(clazz, itemTypes);
+ } catch (BadParcelableException e) {
+ if (sShouldDefuse) {
+ Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
+ return null;
+ } else {
+ throw e;
+ }
+ }
+ mMap.setValueAt(i, object);
+ mLazyValues--;
+ if (mOwnsLazyValues) {
+ Preconditions.checkState(mLazyValues >= 0,
+ "Lazy values ref count below 0");
+ // No more lazy values in mMap, so we can recycle the parcel early rather than
+ // waiting for the next GC run
+ if (mLazyValues == 0) {
+ Preconditions.checkState(mWeakParcelledData.get() != null,
+ "Parcel recycled earlier than expected");
+ recycleParcel(mWeakParcelledData.get());
+ mWeakParcelledData = null;
+ }
+ }
+ }
+ return object;
+ }
+
+ private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean ownsParcel,
+ boolean parcelledByNative) {
+ 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();
+ }
+ mParcelledByNative = false;
+ mParcelledData = null;
+ 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);
+ }
+ int numLazyValues = 0;
+ try {
+ numLazyValues = parcelledData.readArrayMap(map, count, !parcelledByNative,
+ /* lazy */ ownsParcel, mClassLoader);
+ } catch (BadParcelableException e) {
+ if (sShouldDefuse) {
+ Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
+ map.erase();
+ } else {
+ throw e;
+ }
+ } finally {
+ mWeakParcelledData = null;
+ if (ownsParcel) {
+ if (numLazyValues == 0) {
+ recycleParcel(parcelledData);
+ } else {
+ mWeakParcelledData = new WeakReference<>(parcelledData);
+ }
+ }
+
+ mLazyValues = numLazyValues;
+ mParcelledByNative = false;
+ mMap = map;
+ // Set field last as it is volatile
+ mParcelledData = null;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + " final map: " + mMap);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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();
+ }
+ }
+
+ /**
+ * Returns the backing map of this bundle after deserializing every item.
+ *
+ * <p><b>Warning:</b> This method will deserialize every item on the bundle, including custom
+ * types such as {@link Parcelable} and {@link Serializable}, so only use this when you trust
+ * the source. Specifically don't use this method on app-provided bundles.
+ *
+ * @hide
+ */
+ ArrayMap<String, Object> getItemwiseMap() {
+ unparcel(/* itemwise */ true);
+ 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();
+ }
+
+ /**
+ * This method returns true when the parcel is 'definitely' empty.
+ * That is, it may return false for an empty parcel. But will never return true for a non-empty
+ * one.
+ *
+ * @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 isDefinitelyEmpty() {
+ 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(@Nullable BaseBundle a, @Nullable BaseBundle b) {
+ return (a == b) || (a != null && a.kindofEquals(b));
+ }
+
+ /**
+ * Performs a loose equality check, which means there can be false negatives but if the method
+ * returns true than both objects are guaranteed to be equal.
+ *
+ * The point is that this method is a light-weight check in performance terms.
+ *
+ * @hide
+ */
+ public boolean kindofEquals(BaseBundle other) {
+ if (other == null) {
+ return false;
+ }
+ if (isDefinitelyEmpty() && other.isDefinitelyEmpty()) {
+ return true;
+ }
+ if (isParcelled() != other.isParcelled()) {
+ // Big kind-of here!
+ return false;
+ } else if (isParcelled()) {
+ return mParcelledData.compareData(other.mParcelledData) == 0;
+ } else {
+ // Following semantic above of failing in case we get a serialized value vs a
+ // deserialized one, we'll compare the map. If a certain element hasn't been
+ // deserialized yet, it's a function object (or more specifically a LazyValue, but let's
+ // pretend we don't know that here :P), we'll use that element's equality comparison as
+ // map naturally does. That will takes care of comparing the payload if needed (see
+ // Parcel.readLazyValue() for details).
+ return mMap.equals(other.mMap);
+ }
+ }
+
+ /**
+ * Removes all elements from the mapping of this Bundle.
+ * Recycles the underlying parcel if it is still present.
+ */
+ public void clear() {
+ unparcel();
+ if (mOwnsLazyValues && mWeakParcelledData != null) {
+ recycleParcel(mWeakParcelledData.get());
+ }
+
+ mWeakParcelledData = null;
+ mLazyValues = 0;
+ mOwnsLazyValues = true;
+ mMap.clear();
+ }
+
+ private 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;
+ }
+
+ private 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
+ *
+ * @deprecated Use the type-safe specific APIs depending on the type of the item to be
+ * retrieved, eg. {@link #getString(String)}.
+ */
+ @Deprecated
+ @Nullable
+ public Object get(String key) {
+ unparcel();
+ return getValue(key);
+ }
+
+ /**
+ * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <p>Use the more specific APIs where possible, especially in the case of containers such as
+ * lists, since those APIs allow you to specify the type of the items.
+ *
+ * @param key String key
+ * @param clazz The type of the object expected
+ * @return an Object, or null
+ */
+ @Nullable
+ <T> T get(@Nullable String key, @NonNull Class<T> clazz) {
+ unparcel();
+ try {
+ return getValue(key, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, clazz.getCanonicalName(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 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();
+ }
+
+ /** {@hide} */
+ public void putObject(@Nullable String key, @Nullable Object value) {
+ if (value == null) {
+ putString(key, null);
+ } else if (value instanceof Boolean) {
+ putBoolean(key, (Boolean) value);
+ } else if (value instanceof Integer) {
+ putInt(key, (Integer) value);
+ } else if (value instanceof Long) {
+ putLong(key, (Long) value);
+ } else if (value instanceof Double) {
+ putDouble(key, (Double) value);
+ } else if (value instanceof String) {
+ putString(key, (String) value);
+ } else if (value instanceof boolean[]) {
+ putBooleanArray(key, (boolean[]) value);
+ } else if (value instanceof int[]) {
+ putIntArray(key, (int[]) value);
+ } else if (value instanceof long[]) {
+ putLongArray(key, (long[]) value);
+ } else if (value instanceof double[]) {
+ putDoubleArray(key, (double[]) value);
+ } else if (value instanceof String[]) {
+ putStringArray(key, (String[]) value);
+ } else {
+ throw new IllegalArgumentException("Unsupported type " + value.getClass());
+ }
+ }
+
+ /**
+ * 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, @Nullable Object value, String className,
+ Object defaultValue, RuntimeException e) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Key ");
+ sb.append(key);
+ sb.append(" expected ");
+ sb.append(className);
+ if (value != null) {
+ sb.append(" but value was a ");
+ sb.append(value.getClass().getName());
+ } else {
+ sb.append(" but value was of a different type");
+ }
+ 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, @Nullable Object value, String className, RuntimeException e) {
+ typeWarning(key, value, className, "<null>", e);
+ }
+
+ void typeWarning(String key, String className, RuntimeException e) {
+ typeWarning(key, /* value */ null, 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
+ *
+ * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in
+ * other deprecated APIs.
+ */
+ @Deprecated
+ @Nullable
+ Serializable getSerializable(@Nullable String key) {
+ unparcel();
+ Object o = getValue(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 {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * @param key a String, or null
+ * @param clazz The expected class of the returned type
+ * @return a Serializable value, or null
+ */
+ @Nullable
+ <T extends Serializable> T getSerializable(@Nullable String key, @NonNull Class<T> clazz) {
+ return get(key, clazz);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ <T> ArrayList<T> getArrayList(@Nullable String key, @NonNull Class<? extends T> clazz) {
+ unparcel();
+ try {
+ return getValue(key, ArrayList.class, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", 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) {
+ return getArrayList(key, Integer.class);
+ }
+
+ /**
+ * 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) {
+ return getArrayList(key, String.class);
+ }
+
+ /**
+ * 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) {
+ return getArrayList(key, CharSequence.class);
+ }
+
+ /**
+ * 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(/* itemwise */ true);
+ }
+ // 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); // placeholder, 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) {
+ mParcelledByNative = false;
+ // Empty Bundle or end of data.
+ mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
+ 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, it's better to deserialize immediately
+ // otherwise the helper would have to either maintain valid state long after the bundle
+ // had been constructed with parcel or to make sure they trigger deserialization of the
+ // bundle immediately; neither of which is obvious.
+ synchronized (this) {
+ mOwnsLazyValues = false;
+ initializeFromParcelLocked(parcel, /*ownsParcel*/ 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);
+
+ mOwnsLazyValues = true;
+ mParcelledByNative = isNativeBundle;
+ mParcelledData = p;
+ }
+
+ /** {@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.getItemwiseMap();
+ for (int i = 0; i < map.size(); i++) {
+ dumpStats(pw, map.keyAt(i), map.valueAt(i));
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/android-34/android/os/BatteryConsumer.java b/android-34/android/os/BatteryConsumer.java
new file mode 100644
index 0000000..0ba8d51
--- /dev/null
+++ b/android-34/android/os/BatteryConsumer.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.database.CursorWindow;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Interface for objects containing battery attribution data.
+ *
+ * @hide
+ */
+public abstract class BatteryConsumer {
+
+ private static final String TAG = "BatteryConsumer";
+
+ /**
+ * Power usage component, describing the particular part of the system
+ * responsible for power drain.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"POWER_COMPONENT_"}, value = {
+ POWER_COMPONENT_ANY,
+ POWER_COMPONENT_SCREEN,
+ POWER_COMPONENT_CPU,
+ POWER_COMPONENT_BLUETOOTH,
+ POWER_COMPONENT_CAMERA,
+ POWER_COMPONENT_AUDIO,
+ POWER_COMPONENT_VIDEO,
+ POWER_COMPONENT_FLASHLIGHT,
+ POWER_COMPONENT_MOBILE_RADIO,
+ POWER_COMPONENT_SYSTEM_SERVICES,
+ POWER_COMPONENT_SENSORS,
+ POWER_COMPONENT_GNSS,
+ POWER_COMPONENT_WIFI,
+ POWER_COMPONENT_WAKELOCK,
+ POWER_COMPONENT_MEMORY,
+ POWER_COMPONENT_PHONE,
+ POWER_COMPONENT_IDLE,
+ POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public static @interface PowerComponent {
+ }
+
+ public static final int POWER_COMPONENT_ANY = -1;
+ public static final int POWER_COMPONENT_SCREEN = OsProtoEnums.POWER_COMPONENT_SCREEN; // 0
+ public static final int POWER_COMPONENT_CPU = OsProtoEnums.POWER_COMPONENT_CPU; // 1
+ public static final int POWER_COMPONENT_BLUETOOTH = OsProtoEnums.POWER_COMPONENT_BLUETOOTH; // 2
+ public static final int POWER_COMPONENT_CAMERA = OsProtoEnums.POWER_COMPONENT_CAMERA; // 3
+ public static final int POWER_COMPONENT_AUDIO = OsProtoEnums.POWER_COMPONENT_AUDIO; // 4
+ public static final int POWER_COMPONENT_VIDEO = OsProtoEnums.POWER_COMPONENT_VIDEO; // 5
+ public static final int POWER_COMPONENT_FLASHLIGHT =
+ OsProtoEnums.POWER_COMPONENT_FLASHLIGHT; // 6
+ public static final int POWER_COMPONENT_SYSTEM_SERVICES =
+ OsProtoEnums.POWER_COMPONENT_SYSTEM_SERVICES; // 7
+ public static final int POWER_COMPONENT_MOBILE_RADIO =
+ OsProtoEnums.POWER_COMPONENT_MOBILE_RADIO; // 8
+ public static final int POWER_COMPONENT_SENSORS = OsProtoEnums.POWER_COMPONENT_SENSORS; // 9
+ public static final int POWER_COMPONENT_GNSS = OsProtoEnums.POWER_COMPONENT_GNSS; // 10
+ public static final int POWER_COMPONENT_WIFI = OsProtoEnums.POWER_COMPONENT_WIFI; // 11
+ public static final int POWER_COMPONENT_WAKELOCK = OsProtoEnums.POWER_COMPONENT_WAKELOCK; // 12
+ public static final int POWER_COMPONENT_MEMORY = OsProtoEnums.POWER_COMPONENT_MEMORY; // 13
+ public static final int POWER_COMPONENT_PHONE = OsProtoEnums.POWER_COMPONENT_PHONE; // 14
+ public static final int POWER_COMPONENT_AMBIENT_DISPLAY =
+ OsProtoEnums.POWER_COMPONENT_AMBIENT_DISPLAY; // 15
+ public static final int POWER_COMPONENT_IDLE = OsProtoEnums.POWER_COMPONENT_IDLE; // 16
+ // Power that is re-attributed to other battery consumers. For example, for System Server
+ // this represents the power attributed to apps requesting system services.
+ // The value should be negative or zero.
+ public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS =
+ OsProtoEnums.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS; // 17
+
+ public static final int POWER_COMPONENT_COUNT = 18;
+
+ public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
+ public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
+
+ private static final String[] sPowerComponentNames = new String[POWER_COMPONENT_COUNT];
+
+ static {
+ // Assign individually to avoid future mismatch
+ sPowerComponentNames[POWER_COMPONENT_SCREEN] = "screen";
+ sPowerComponentNames[POWER_COMPONENT_CPU] = "cpu";
+ sPowerComponentNames[POWER_COMPONENT_BLUETOOTH] = "bluetooth";
+ sPowerComponentNames[POWER_COMPONENT_CAMERA] = "camera";
+ sPowerComponentNames[POWER_COMPONENT_AUDIO] = "audio";
+ sPowerComponentNames[POWER_COMPONENT_VIDEO] = "video";
+ sPowerComponentNames[POWER_COMPONENT_FLASHLIGHT] = "flashlight";
+ sPowerComponentNames[POWER_COMPONENT_SYSTEM_SERVICES] = "system_services";
+ sPowerComponentNames[POWER_COMPONENT_MOBILE_RADIO] = "mobile_radio";
+ sPowerComponentNames[POWER_COMPONENT_SENSORS] = "sensors";
+ sPowerComponentNames[POWER_COMPONENT_GNSS] = "gnss";
+ sPowerComponentNames[POWER_COMPONENT_WIFI] = "wifi";
+ sPowerComponentNames[POWER_COMPONENT_WAKELOCK] = "wakelock";
+ sPowerComponentNames[POWER_COMPONENT_MEMORY] = "memory";
+ sPowerComponentNames[POWER_COMPONENT_PHONE] = "phone";
+ sPowerComponentNames[POWER_COMPONENT_AMBIENT_DISPLAY] = "ambient_display";
+ sPowerComponentNames[POWER_COMPONENT_IDLE] = "idle";
+ sPowerComponentNames[POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS] = "reattributed";
+ }
+
+ /**
+ * Identifiers of models used for power estimation.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"POWER_MODEL_"}, value = {
+ POWER_MODEL_UNDEFINED,
+ POWER_MODEL_POWER_PROFILE,
+ POWER_MODEL_ENERGY_CONSUMPTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PowerModel {
+ }
+
+ /**
+ * Unspecified power model.
+ */
+ public static final int POWER_MODEL_UNDEFINED = 0;
+
+ /**
+ * Power model that is based on average consumption rates that hardware components
+ * consume in various states.
+ */
+ public static final int POWER_MODEL_POWER_PROFILE = 1;
+
+ /**
+ * Power model that is based on energy consumption stats provided by PowerStats HAL.
+ */
+ public static final int POWER_MODEL_ENERGY_CONSUMPTION = 2;
+
+ /**
+ * Identifiers of consumed power aggregations.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"PROCESS_STATE_"}, value = {
+ PROCESS_STATE_ANY,
+ PROCESS_STATE_UNSPECIFIED,
+ PROCESS_STATE_FOREGROUND,
+ PROCESS_STATE_BACKGROUND,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_CACHED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProcessState {
+ }
+
+ public static final int PROCESS_STATE_UNSPECIFIED = 0;
+ public static final int PROCESS_STATE_ANY = PROCESS_STATE_UNSPECIFIED;
+ public static final int PROCESS_STATE_FOREGROUND = 1;
+ public static final int PROCESS_STATE_BACKGROUND = 2;
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+ public static final int PROCESS_STATE_CACHED = 4;
+
+ public static final int PROCESS_STATE_COUNT = 5;
+
+ private static final String[] sProcessStateNames = new String[PROCESS_STATE_COUNT];
+
+ static {
+ // Assign individually to avoid future mismatch
+ sProcessStateNames[PROCESS_STATE_UNSPECIFIED] = "unspecified";
+ sProcessStateNames[PROCESS_STATE_FOREGROUND] = "fg";
+ sProcessStateNames[PROCESS_STATE_BACKGROUND] = "bg";
+ sProcessStateNames[PROCESS_STATE_FOREGROUND_SERVICE] = "fgs";
+ sProcessStateNames[PROCESS_STATE_CACHED] = "cached";
+ }
+
+ private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = {
+ POWER_COMPONENT_CPU,
+ POWER_COMPONENT_MOBILE_RADIO,
+ POWER_COMPONENT_WIFI,
+ POWER_COMPONENT_BLUETOOTH,
+ };
+
+ static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
+ static final int COLUMN_COUNT = 1;
+
+ /**
+ * Identifies power attribution dimensions that a caller is interested in.
+ */
+ public static final class Dimensions {
+ public final @PowerComponent int powerComponent;
+ public final @ProcessState int processState;
+
+ public Dimensions(int powerComponent, int processState) {
+ this.powerComponent = powerComponent;
+ this.processState = processState;
+ }
+
+ @Override
+ public String toString() {
+ boolean dimensionSpecified = false;
+ StringBuilder sb = new StringBuilder();
+ if (powerComponent != POWER_COMPONENT_ANY) {
+ sb.append("powerComponent=").append(sPowerComponentNames[powerComponent]);
+ dimensionSpecified = true;
+ }
+ if (processState != PROCESS_STATE_UNSPECIFIED) {
+ if (dimensionSpecified) {
+ sb.append(", ");
+ }
+ sb.append("processState=").append(sProcessStateNames[processState]);
+ dimensionSpecified = true;
+ }
+ if (!dimensionSpecified) {
+ sb.append("any components and process states");
+ }
+ return sb.toString();
+ }
+ }
+
+ public static final Dimensions UNSPECIFIED_DIMENSIONS =
+ new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY);
+
+ /**
+ * Identifies power attribution dimensions that are captured by an data element of
+ * a BatteryConsumer. These Keys are used to access those values and to set them using
+ * Builders. See for example {@link #getConsumedPower(Key)}.
+ *
+ * Keys cannot be allocated by the client - they can only be obtained by calling
+ * {@link #getKeys} or {@link #getKey}. All BatteryConsumers that are part of the
+ * same BatteryUsageStats share the same set of keys, therefore it is safe to obtain
+ * the keys from one BatteryConsumer and apply them to other BatteryConsumers
+ * in the same BatteryUsageStats.
+ */
+ public static final class Key {
+ public final @PowerComponent int powerComponent;
+ public final @ProcessState int processState;
+
+ final int mPowerModelColumnIndex;
+ final int mPowerColumnIndex;
+ final int mDurationColumnIndex;
+ private String mShortString;
+
+ private Key(int powerComponent, int processState, int powerModelColumnIndex,
+ int powerColumnIndex, int durationColumnIndex) {
+ this.powerComponent = powerComponent;
+ this.processState = processState;
+
+ mPowerModelColumnIndex = powerModelColumnIndex;
+ mPowerColumnIndex = powerColumnIndex;
+ mDurationColumnIndex = durationColumnIndex;
+ }
+
+ @SuppressWarnings("EqualsUnsafeCast")
+ @Override
+ public boolean equals(Object o) {
+ // Skipping null and class check for performance
+ final Key key = (Key) o;
+ return powerComponent == key.powerComponent
+ && processState == key.processState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = powerComponent;
+ result = 31 * result + processState;
+ return result;
+ }
+
+ /**
+ * Returns a string suitable for use in dumpsys.
+ */
+ public String toShortString() {
+ if (mShortString == null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(powerComponentIdToString(powerComponent));
+ if (processState != PROCESS_STATE_UNSPECIFIED) {
+ sb.append(':');
+ sb.append(processStateToString(processState));
+ }
+ mShortString = sb.toString();
+ }
+ return mShortString;
+ }
+ }
+
+ protected final BatteryConsumerData mData;
+ protected final PowerComponents mPowerComponents;
+
+ protected BatteryConsumer(BatteryConsumerData data, @NonNull PowerComponents powerComponents) {
+ mData = data;
+ mPowerComponents = powerComponents;
+ }
+
+ public BatteryConsumer(BatteryConsumerData data) {
+ mData = data;
+ mPowerComponents = new PowerComponents(data);
+ }
+
+ /**
+ * Total power consumed by this consumer, in mAh.
+ */
+ public double getConsumedPower() {
+ return mPowerComponents.getConsumedPower(UNSPECIFIED_DIMENSIONS);
+ }
+
+ /**
+ * Returns power consumed aggregated over the specified dimensions, in mAh.
+ */
+ public double getConsumedPower(Dimensions dimensions) {
+ return mPowerComponents.getConsumedPower(dimensions);
+ }
+
+ /**
+ * Returns keys for various power values attributed to the specified component
+ * held by this BatteryUsageStats object.
+ */
+ public Key[] getKeys(@PowerComponent int componentId) {
+ return mData.getKeys(componentId);
+ }
+
+ /**
+ * Returns the key for the power attributed to the specified component,
+ * for all values of other dimensions such as process state.
+ */
+ public Key getKey(@PowerComponent int componentId) {
+ return mData.getKey(componentId, PROCESS_STATE_UNSPECIFIED);
+ }
+
+ /**
+ * Returns the key for the power attributed to the specified component and process state.
+ */
+ public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
+ return mData.getKey(componentId, processState);
+ }
+
+ /**
+ * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
+ *
+ * @param componentId The ID of the power component, e.g.
+ * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ * @return Amount of consumed power in mAh.
+ */
+ public double getConsumedPower(@PowerComponent int componentId) {
+ return mPowerComponents.getConsumedPower(
+ mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
+ }
+
+ /**
+ * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
+ *
+ * @param key The key of the power component, obtained by calling {@link #getKey} or
+ * {@link #getKeys} method.
+ * @return Amount of consumed power in mAh.
+ */
+ public double getConsumedPower(@NonNull Key key) {
+ return mPowerComponents.getConsumedPower(key);
+ }
+
+ /**
+ * Returns the ID of the model that was used for power estimation.
+ *
+ * @param componentId The ID of the power component, e.g.
+ * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ */
+ public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
+ return mPowerComponents.getPowerModel(
+ mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
+ }
+
+ /**
+ * Returns the ID of the model that was used for power estimation.
+ *
+ * @param key The key of the power component, obtained by calling {@link #getKey} or
+ * {@link #getKeys} method.
+ */
+ public @PowerModel int getPowerModel(@NonNull BatteryConsumer.Key key) {
+ return mPowerComponents.getPowerModel(key);
+ }
+
+ /**
+ * Returns the amount of drain attributed to the specified custom drain type.
+ *
+ * @param componentId The ID of the custom power component.
+ * @return Amount of consumed power in mAh.
+ */
+ public double getConsumedPowerForCustomComponent(int componentId) {
+ return mPowerComponents.getConsumedPowerForCustomComponent(componentId);
+ }
+
+ public int getCustomPowerComponentCount() {
+ return mData.layout.customPowerComponentCount;
+ }
+
+ /**
+ * Returns the name of the specified power component.
+ *
+ * @param componentId The ID of the custom power component.
+ */
+ public String getCustomPowerComponentName(int componentId) {
+ return mPowerComponents.getCustomPowerComponentName(componentId);
+ }
+
+ /**
+ * Returns the amount of time since BatteryStats reset used by the specified component, e.g.
+ * CPU, WiFi etc.
+ *
+ * @param componentId The ID of the power component, e.g.
+ * {@link UidBatteryConsumer#POWER_COMPONENT_CPU}.
+ * @return Amount of time in milliseconds.
+ */
+ public long getUsageDurationMillis(@PowerComponent int componentId) {
+ return mPowerComponents.getUsageDurationMillis(getKey(componentId));
+ }
+
+ /**
+ * Returns the amount of time since BatteryStats reset used by the specified component, e.g.
+ * CPU, WiFi etc.
+ *
+ *
+ * @param key The key of the power component, obtained by calling {@link #getKey} or
+ * {@link #getKeys} method.
+ * @return Amount of time in milliseconds.
+ */
+ public long getUsageDurationMillis(@NonNull Key key) {
+ return mPowerComponents.getUsageDurationMillis(key);
+ }
+
+ /**
+ * Returns the amount of usage time attributed to the specified custom component
+ * since BatteryStats reset.
+ *
+ * @param componentId The ID of the custom power component.
+ * @return Amount of time in milliseconds.
+ */
+ public long getUsageDurationForCustomComponentMillis(int componentId) {
+ return mPowerComponents.getUsageDurationForCustomComponentMillis(componentId);
+ }
+
+ /**
+ * Returns the name of the specified component. Intended for logging and debugging.
+ */
+ public static String powerComponentIdToString(@BatteryConsumer.PowerComponent int componentId) {
+ if (componentId == POWER_COMPONENT_ANY) {
+ return "all";
+ }
+ return sPowerComponentNames[componentId];
+ }
+
+ /**
+ * Returns the name of the specified power model. Intended for logging and debugging.
+ */
+ public static String powerModelToString(@BatteryConsumer.PowerModel int powerModel) {
+ switch (powerModel) {
+ case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
+ return "energy consumption";
+ case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+ return "power profile";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Returns the equivalent PowerModel enum for the specified power model.
+ * {@see BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage.PowerModel}
+ */
+ public static int powerModelToProtoEnum(@BatteryConsumer.PowerModel int powerModel) {
+ switch (powerModel) {
+ case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
+ return BatteryUsageStatsAtomsProto.PowerComponentModel.MEASURED_ENERGY;
+ case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+ return BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_PROFILE;
+ default:
+ return BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED;
+ }
+ }
+
+ /**
+ * Returns the name of the specified process state. Intended for logging and debugging.
+ */
+ public static String processStateToString(@BatteryConsumer.ProcessState int processState) {
+ return sProcessStateNames[processState];
+ }
+
+ /**
+ * Prints the stats in a human-readable format.
+ */
+ public void dump(PrintWriter pw) {
+ dump(pw, true);
+ }
+
+ /**
+ * Prints the stats in a human-readable format.
+ *
+ * @param skipEmptyComponents if true, omit any power components with a zero amount.
+ */
+ public abstract void dump(PrintWriter pw, boolean skipEmptyComponents);
+
+ /** Returns whether there are any atoms.proto BATTERY_CONSUMER_DATA data to write to a proto. */
+ boolean hasStatsProtoData() {
+ return writeStatsProtoImpl(null, /* Irrelevant fieldId: */ 0);
+ }
+
+ /** Writes the atoms.proto BATTERY_CONSUMER_DATA for this BatteryConsumer to the given proto. */
+ void writeStatsProto(@NonNull ProtoOutputStream proto, long fieldId) {
+ writeStatsProtoImpl(proto, fieldId);
+ }
+
+ /**
+ * Returns whether there are any atoms.proto BATTERY_CONSUMER_DATA data to write to a proto,
+ * and writes it to the given proto if it is non-null.
+ */
+ private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto, long fieldId) {
+ final long totalConsumedPowerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower());
+
+ if (totalConsumedPowerDeciCoulombs == 0) {
+ // NOTE: Strictly speaking we should also check !mPowerComponents.hasStatsProtoData().
+ // However, that call is a bit expensive (a for loop). And the only way that
+ // totalConsumedPower can be 0 while mPowerComponents.hasStatsProtoData() is true is
+ // if POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS (which is the only negative
+ // allowed) happens to exactly equal the sum of all other components, which
+ // can't really happen in practice.
+ // So we'll just adopt the rule "if total==0, don't write any details".
+ // If negative values are used for other things in the future, this can be revisited.
+ return false;
+ }
+ if (proto == null) {
+ // We're just asked whether there is data, not to actually write it. And there is.
+ return true;
+ }
+
+ final long token = proto.start(fieldId);
+ proto.write(
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.TOTAL_CONSUMED_POWER_DECI_COULOMBS,
+ totalConsumedPowerDeciCoulombs);
+ mPowerComponents.writeStatsProto(proto);
+ proto.end(token);
+
+ return true;
+ }
+
+ /** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
+ static long convertMahToDeciCoulombs(double powerMah) {
+ return (long) (powerMah * (10 * 3600 / 1000) + 0.5);
+ }
+
+ static class BatteryConsumerData {
+ private final CursorWindow mCursorWindow;
+ private final int mCursorRow;
+ public final BatteryConsumerDataLayout layout;
+
+ BatteryConsumerData(CursorWindow cursorWindow, int cursorRow,
+ BatteryConsumerDataLayout layout) {
+ mCursorWindow = cursorWindow;
+ mCursorRow = cursorRow;
+ this.layout = layout;
+ }
+
+ @Nullable
+ static BatteryConsumerData create(CursorWindow cursorWindow,
+ BatteryConsumerDataLayout layout) {
+ int cursorRow = cursorWindow.getNumRows();
+ if (!cursorWindow.allocRow()) {
+ Slog.e(TAG, "Cannot allocate BatteryConsumerData: too many UIDs: " + cursorRow);
+ cursorRow = -1;
+ }
+ return new BatteryConsumerData(cursorWindow, cursorRow, layout);
+ }
+
+ public Key[] getKeys(int componentId) {
+ return layout.keys[componentId];
+ }
+
+ Key getKeyOrThrow(int componentId, int processState) {
+ Key key = getKey(componentId, processState);
+ if (key == null) {
+ if (processState == PROCESS_STATE_ANY) {
+ throw new IllegalArgumentException(
+ "Unsupported power component ID: " + componentId);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported power component ID: " + componentId
+ + " process state: " + processState);
+ }
+ }
+ return key;
+ }
+
+ Key getKey(int componentId, int processState) {
+ if (componentId >= POWER_COMPONENT_COUNT) {
+ return null;
+ }
+
+ if (processState == PROCESS_STATE_ANY) {
+ // The 0-th key for each component corresponds to the roll-up,
+ // across all dimensions. We might as well skip the iteration over the array.
+ return layout.keys[componentId][0];
+ } else {
+ for (Key key : layout.keys[componentId]) {
+ if (key.processState == processState) {
+ return key;
+ }
+ }
+ }
+ return null;
+ }
+
+ void putInt(int columnIndex, int value) {
+ if (mCursorRow == -1) {
+ return;
+ }
+ mCursorWindow.putLong(value, mCursorRow, columnIndex);
+ }
+
+ int getInt(int columnIndex) {
+ if (mCursorRow == -1) {
+ return 0;
+ }
+ return mCursorWindow.getInt(mCursorRow, columnIndex);
+ }
+
+ void putDouble(int columnIndex, double value) {
+ if (mCursorRow == -1) {
+ return;
+ }
+ mCursorWindow.putDouble(value, mCursorRow, columnIndex);
+ }
+
+ double getDouble(int columnIndex) {
+ if (mCursorRow == -1) {
+ return 0;
+ }
+ return mCursorWindow.getDouble(mCursorRow, columnIndex);
+ }
+
+ void putLong(int columnIndex, long value) {
+ if (mCursorRow == -1) {
+ return;
+ }
+ mCursorWindow.putLong(value, mCursorRow, columnIndex);
+ }
+
+ long getLong(int columnIndex) {
+ if (mCursorRow == -1) {
+ return 0;
+ }
+ return mCursorWindow.getLong(mCursorRow, columnIndex);
+ }
+
+ void putString(int columnIndex, String value) {
+ if (mCursorRow == -1) {
+ return;
+ }
+ mCursorWindow.putString(value, mCursorRow, columnIndex);
+ }
+
+ String getString(int columnIndex) {
+ if (mCursorRow == -1) {
+ return null;
+ }
+ return mCursorWindow.getString(mCursorRow, columnIndex);
+ }
+ }
+
+ static class BatteryConsumerDataLayout {
+ private static final Key[] KEY_ARRAY = new Key[0];
+ public final String[] customPowerComponentNames;
+ public final int customPowerComponentCount;
+ public final boolean powerModelsIncluded;
+ public final boolean processStateDataIncluded;
+ public final Key[][] keys;
+ public final int totalConsumedPowerColumnIndex;
+ public final int firstCustomConsumedPowerColumn;
+ public final int firstCustomUsageDurationColumn;
+ public final int columnCount;
+ public final Key[][] processStateKeys;
+
+ private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
+ boolean powerModelsIncluded, boolean includeProcessStateData) {
+ this.customPowerComponentNames = customPowerComponentNames;
+ this.customPowerComponentCount = customPowerComponentNames.length;
+ this.powerModelsIncluded = powerModelsIncluded;
+ this.processStateDataIncluded = includeProcessStateData;
+
+ int columnIndex = firstColumn;
+
+ totalConsumedPowerColumnIndex = columnIndex++;
+
+ keys = new Key[POWER_COMPONENT_COUNT][];
+
+ ArrayList<Key> perComponentKeys = new ArrayList<>();
+ for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
+ perComponentKeys.clear();
+
+ // Declare the Key for the power component, ignoring other dimensions.
+ perComponentKeys.add(
+ new Key(componentId, PROCESS_STATE_ANY,
+ powerModelsIncluded ? columnIndex++ : -1, // power model
+ columnIndex++, // power
+ columnIndex++ // usage duration
+ ));
+
+ // Declare Keys for all process states, if needed
+ if (includeProcessStateData) {
+ boolean isSupported = false;
+ for (int id : SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE) {
+ if (id == componentId) {
+ isSupported = true;
+ break;
+ }
+ }
+ if (isSupported) {
+ for (int processState = 0; processState < PROCESS_STATE_COUNT;
+ processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ perComponentKeys.add(
+ new Key(componentId, processState,
+ powerModelsIncluded ? columnIndex++ : -1, // power model
+ columnIndex++, // power
+ columnIndex++ // usage duration
+ ));
+ }
+ }
+ }
+
+ keys[componentId] = perComponentKeys.toArray(KEY_ARRAY);
+ }
+
+ if (includeProcessStateData) {
+ processStateKeys = new Key[BatteryConsumer.PROCESS_STATE_COUNT][];
+ ArrayList<Key> perProcStateKeys = new ArrayList<>();
+ for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ perProcStateKeys.clear();
+ for (int i = 0; i < keys.length; i++) {
+ for (int j = 0; j < keys[i].length; j++) {
+ if (keys[i][j].processState == processState) {
+ perProcStateKeys.add(keys[i][j]);
+ }
+ }
+ }
+ processStateKeys[processState] = perProcStateKeys.toArray(KEY_ARRAY);
+ }
+ } else {
+ processStateKeys = null;
+ }
+
+ firstCustomConsumedPowerColumn = columnIndex;
+ columnIndex += customPowerComponentCount;
+
+ firstCustomUsageDurationColumn = columnIndex;
+ columnIndex += customPowerComponentCount;
+
+ columnCount = columnIndex;
+ }
+ }
+
+ static BatteryConsumerDataLayout createBatteryConsumerDataLayout(
+ String[] customPowerComponentNames, boolean includePowerModels,
+ boolean includeProcessStateData) {
+ int columnCount = BatteryConsumer.COLUMN_COUNT;
+ columnCount = Math.max(columnCount, AggregateBatteryConsumer.COLUMN_COUNT);
+ columnCount = Math.max(columnCount, UidBatteryConsumer.COLUMN_COUNT);
+ columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT);
+
+ return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames,
+ includePowerModels, includeProcessStateData);
+ }
+
+ protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
+ protected final BatteryConsumer.BatteryConsumerData mData;
+ protected final PowerComponents.Builder mPowerComponentsBuilder;
+
+ public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType) {
+ mData = data;
+ data.putLong(COLUMN_INDEX_BATTERY_CONSUMER_TYPE, consumerType);
+
+ mPowerComponentsBuilder = new PowerComponents.Builder(data);
+ }
+
+ @Nullable
+ public Key[] getKeys(@PowerComponent int componentId) {
+ return mData.getKeys(componentId);
+ }
+
+ @Nullable
+ public Key getKey(@PowerComponent int componentId, @ProcessState int processState) {
+ return mData.getKey(componentId, processState);
+ }
+
+ /**
+ * Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
+ *
+ * @param componentId The ID of the power component, e.g.
+ * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ * @param componentPower Amount of consumed power in mAh.
+ */
+ @NonNull
+ public T setConsumedPower(@PowerComponent int componentId, double componentPower) {
+ return setConsumedPower(componentId, componentPower, POWER_MODEL_POWER_PROFILE);
+ }
+
+ /**
+ * Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
+ *
+ * @param componentId The ID of the power component, e.g.
+ * {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ * @param componentPower Amount of consumed power in mAh.
+ */
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setConsumedPower(@PowerComponent int componentId, double componentPower,
+ @PowerModel int powerModel) {
+ mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
+ componentPower, powerModel);
+ return (T) this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
+ mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel);
+ return (T) this;
+ }
+
+ /**
+ * Sets the amount of drain attributed to the specified custom drain type.
+ *
+ * @param componentId The ID of the custom power component.
+ * @param componentPower Amount of consumed power in mAh.
+ */
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setConsumedPowerForCustomComponent(int componentId, double componentPower) {
+ mPowerComponentsBuilder.setConsumedPowerForCustomComponent(componentId, componentPower);
+ return (T) this;
+ }
+
+ /**
+ * Sets the amount of time used by the specified component, e.g. CPU, WiFi etc.
+ *
+ * @param componentId The ID of the power component, e.g.
+ * {@link UidBatteryConsumer#POWER_COMPONENT_CPU}.
+ * @param componentUsageTimeMillis Amount of time in microseconds.
+ */
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setUsageDurationMillis(@UidBatteryConsumer.PowerComponent int componentId,
+ long componentUsageTimeMillis) {
+ mPowerComponentsBuilder
+ .setUsageDurationMillis(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
+ componentUsageTimeMillis);
+ return (T) this;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setUsageDurationMillis(Key key, long componentUsageTimeMillis) {
+ mPowerComponentsBuilder.setUsageDurationMillis(key, componentUsageTimeMillis);
+ return (T) this;
+ }
+
+ /**
+ * Sets the amount of time used by the specified custom component.
+ *
+ * @param componentId The ID of the custom power component.
+ * @param componentUsageTimeMillis Amount of time in microseconds.
+ */
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public T setUsageDurationForCustomComponentMillis(int componentId,
+ long componentUsageTimeMillis) {
+ mPowerComponentsBuilder.setUsageDurationForCustomComponentMillis(componentId,
+ componentUsageTimeMillis);
+ return (T) this;
+ }
+
+ /**
+ * Returns the total power accumulated by this builder so far. It may change
+ * by the time the {@code build()} method is called.
+ */
+ public double getTotalPower() {
+ return mPowerComponentsBuilder.getTotalPower();
+ }
+ }
+}
diff --git a/android-34/android/os/BatteryManager.java b/android-34/android/os/BatteryManager.java
new file mode 100644
index 0000000..6bc0f6e
--- /dev/null
+++ b/android-34/android/os/BatteryManager.java
@@ -0,0 +1,514 @@
+/*
+ * 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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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_CHANGED}:
+ * Int value representing the battery charging cycle count.
+ */
+ public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
+
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value representing the battery charging status.
+ */
+ public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
+
+ /**
+ * 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
+ /** Power source is dock. */
+ public static final int BATTERY_PLUGGED_DOCK = OsProtoEnums.BATTERY_PLUGGED_DOCK; // = 8
+
+ // values for "charge policy" property
+ /**
+ * Default policy (e.g. normal).
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_DEFAULT = OsProtoEnums.CHARGING_POLICY_DEFAULT; // = 1
+ /**
+ * Optimized for battery health using static thresholds (e.g stop at 80%).
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_ADAPTIVE_AON =
+ OsProtoEnums.CHARGING_POLICY_ADAPTIVE_AON; // = 2
+ /**
+ * Optimized for battery health using adaptive thresholds.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_ADAPTIVE_AC =
+ OsProtoEnums.CHARGING_POLICY_ADAPTIVE_AC; // = 3
+ /**
+ * Optimized for battery health, devices always connected to power.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
+ OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+
+ /** @hide */
+ public static final int BATTERY_PLUGGED_ANY =
+ BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
+ | BATTERY_PLUGGED_DOCK;
+
+ /**
+ * 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;
+
+ /**
+ * Battery manufacturing date is reported in epoch. The 0 timepoint
+ * begins at midnight Coordinated Universal Time (UTC) on January 1, 1970.
+ * It is a long integer in seconds.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * Example: <code>
+ * // The value returned from the API can be used to create a Date, used
+ * // to set the time on a calendar and coverted to a string.
+ * import java.util.Date;
+ *
+ * mBatteryManager = mContext.getSystemService(BatteryManager.class);
+ * final long manufacturingDate =
+ * mBatteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE);
+ * Date date = new Date(manufacturingDate);
+ * Calendar calendar = Calendar.getInstance();
+ * calendar.setTime(date);
+ * // Convert to yyyy-MM-dd HH:mm:ss format string
+ * SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ * String dateString = sdf.format(date);
+ * </code>
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7;
+
+ /**
+ * The date of first usage is reported in epoch. The 0 timepoint
+ * begins at midnight Coordinated Universal Time (UTC) on January 1, 1970.
+ * It is a long integer in seconds.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * {@link BATTERY_PROPERTY_MANUFACTURING_DATE for sample code}
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8;
+
+ /**
+ * Battery charging policy from a CHARGING_POLICY_* value..
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9;
+
+ /**
+ *
+ * Percentage representing the measured battery state of health (remaining
+ * estimated full charge capacity relative to the rated capacity in %).
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
+
+ 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
+ public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ try {
+ return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android-34/android/os/BatteryManagerInternal.java b/android-34/android/os/BatteryManagerInternal.java
new file mode 100644
index 0000000..9bad0de
--- /dev/null
+++ b/android-34/android/os/BatteryManagerInternal.java
@@ -0,0 +1,119 @@
+/*
+ * 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();
+
+ /**
+ * Returns battery health status as an integer representing the current battery health constant.
+ *
+ * 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 getBatteryHealth();
+
+ /**
+ * 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();
+
+ /**
+ * Sets battery AC charger to enabled/disabled, and freezes the battery state.
+ */
+ public abstract void setChargerAcOnline(boolean online, boolean forceUpdate);
+
+ /**
+ * Sets battery level, and freezes the battery state.
+ */
+ public abstract void setBatteryLevel(int level, boolean forceUpdate);
+
+ /**
+ * Unplugs battery, and freezes the battery state.
+ */
+ public abstract void unplugBattery(boolean forceUpdate);
+
+ /**
+ * Unfreezes battery state, returning to current hardware values.
+ */
+ public abstract void resetBattery(boolean forceUpdate);
+
+ /**
+ * Suspend charging even if plugged in.
+ */
+ public abstract void suspendBatteryInput();
+}
diff --git a/android-34/android/os/BatteryProperty.java b/android-34/android/os/BatteryProperty.java
new file mode 100644
index 0000000..b40988a
--- /dev/null
+++ b/android-34/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-34/android/os/BatterySaverPolicyConfig.java b/android-34/android/os/BatterySaverPolicyConfig.java
new file mode 100644
index 0000000..a999e65
--- /dev/null
+++ b/android-34/android/os/BatterySaverPolicyConfig.java
@@ -0,0 +1,566 @@
+/*
+ * 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 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 final int mSoundTriggerMode;
+
+ 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;
+ 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));
+ mSoundTriggerMode = Math.max(PowerManager.MIN_SOUND_TRIGGER_MODE,
+ Math.min(in.mSoundTriggerMode, PowerManager.MAX_SOUND_TRIGGER_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();
+ 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));
+ mSoundTriggerMode = Math.max(PowerManager.MIN_SOUND_TRIGGER_MODE,
+ Math.min(in.readInt(), PowerManager.MAX_SOUND_TRIGGER_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(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);
+ dest.writeInt(mSoundTriggerMode);
+ }
+
+ @NonNull
+ @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_mode=" + mSoundTriggerMode + ","
+ + "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;
+ }
+
+ /**
+ * Get the SoundTrigger mode while in Battery Saver.
+ */
+ @PowerManager.SoundTriggerPowerSaveMode
+ public int getSoundTriggerMode() {
+ return mSoundTriggerMode;
+ }
+
+ /**
+ * Whether or not to disable {@link android.hardware.soundtrigger.SoundTrigger}
+ * while in Battery Saver.
+ * @deprecated Use {@link #getSoundTriggerMode()} instead.
+ */
+ @Deprecated
+ public boolean getDisableSoundTrigger() {
+ return mSoundTriggerMode == PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+ }
+
+ /** 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 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;
+ private int mSoundTriggerMode = PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+
+ public Builder() {
+ }
+
+ /**
+ * Creates a Builder prepopulated with the values from the passed in
+ * {@link BatterySaverPolicyConfig}.
+ */
+ public Builder(@NonNull BatterySaverPolicyConfig batterySaverPolicyConfig) {
+ mAdjustBrightnessFactor = batterySaverPolicyConfig.getAdjustBrightnessFactor();
+ mAdvertiseIsEnabled = batterySaverPolicyConfig.getAdvertiseIsEnabled();
+ mDeferFullBackup = batterySaverPolicyConfig.getDeferFullBackup();
+ mDeferKeyValueBackup = batterySaverPolicyConfig.getDeferKeyValueBackup();
+
+ for (String key :
+ batterySaverPolicyConfig.getDeviceSpecificSettings().keySet()) {
+ mDeviceSpecificSettings.put(key,
+ batterySaverPolicyConfig.getDeviceSpecificSettings().get(key));
+ }
+
+ mDisableAnimation = batterySaverPolicyConfig.getDisableAnimation();
+ mDisableAod = batterySaverPolicyConfig.getDisableAod();
+ mDisableLaunchBoost = batterySaverPolicyConfig.getDisableLaunchBoost();
+ mDisableOptionalSensors = batterySaverPolicyConfig.getDisableOptionalSensors();
+ mDisableVibration = batterySaverPolicyConfig.getDisableVibration();
+ mEnableAdjustBrightness = batterySaverPolicyConfig.getEnableAdjustBrightness();
+ mEnableDataSaver = batterySaverPolicyConfig.getEnableDataSaver();
+ mEnableFirewall = batterySaverPolicyConfig.getEnableFirewall();
+ mEnableNightMode = batterySaverPolicyConfig.getEnableNightMode();
+ mEnableQuickDoze = batterySaverPolicyConfig.getEnableQuickDoze();
+ mForceAllAppsStandby = batterySaverPolicyConfig.getForceAllAppsStandby();
+ mForceBackgroundCheck = batterySaverPolicyConfig.getForceBackgroundCheck();
+ mLocationMode = batterySaverPolicyConfig.getLocationMode();
+ mSoundTriggerMode = batterySaverPolicyConfig.getSoundTriggerMode();
+ }
+
+ /**
+ * 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.
+ * @deprecated Use {@link #setSoundTriggerMode(int)} instead.
+ */
+ @Deprecated
+ @NonNull
+ public Builder setDisableSoundTrigger(boolean disableSoundTrigger) {
+ if (disableSoundTrigger) {
+ mSoundTriggerMode = PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+ } else {
+ mSoundTriggerMode = PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+ }
+ return this;
+ }
+
+ /**
+ * Set the SoundTrigger mode while in Battery Saver.
+ */
+ @NonNull
+ public Builder setSoundTriggerMode(
+ @PowerManager.SoundTriggerPowerSaveMode int soundTriggerMode) {
+ mSoundTriggerMode = soundTriggerMode;
+ 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-34/android/os/BatteryStats.java b/android-34/android/os/BatteryStats.java
new file mode 100644
index 0000000..2bad670
--- /dev/null
+++ b/android-34/android/os/BatteryStats.java
@@ -0,0 +1,9006 @@
+/*
+ * 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.os.BatteryStatsManager.NUM_WIFI_STATES;
+import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.job.JobParameters;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.location.GnssSignalQuality;
+import android.os.BatteryStatsManager.WifiState;
+import android.os.BatteryStatsManager.WifiSupplState;
+import android.server.ServerProtoEnums;
+import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
+import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
+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.Slog;
+import android.util.SparseArray;
+import android.util.SparseDoubleArray;
+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.os.BatteryStatsHistoryIterator;
+
+import com.google.android.collect.Lists;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+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 {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public BatteryStats() {}
+
+ 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 = Context.BATTERY_STATS_SERVICE;
+
+ /**
+ * A constant indicating a partial wake lock timer.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "STATS_" }, value = {
+ STATS_SINCE_CHARGED,
+ STATS_CURRENT,
+ STATS_SINCE_UNPLUGGED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatName {}
+
+ // 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.
+ * New in version 35:
+ * - Fixed bug that was not reporting high cellular tx power correctly
+ * - Added out of service and emergency service modes to data connection types
+ * New in version 36:
+ * - Added PowerStats and CPU time-in-state data
+ */
+ static final int CHECKIN_VERSION = 36;
+
+ /**
+ * 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 {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public 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);
+
+ /**
+ * Returns the count accumulated by this Counter for the specified process state.
+ * If the counter does not support per-procstate tracking, returns 0.
+ */
+ public abstract long getCountForProcessState(@BatteryConsumer.ProcessState int procState);
+
+ /**
+ * 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 {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public 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 Uid.PROCESS_STATE_NONEXISTENT;
+ } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ return Uid.PROCESS_STATE_TOP;
+ } else if (procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
+ return Uid.PROCESS_STATE_BACKGROUND;
+ } else if (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ 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;
+ }
+ }
+
+ /**
+ * Maps BatteryStats.Uid process state to the BatteryConsumer process state.
+ */
+ public static @BatteryConsumer.ProcessState int
+ mapUidProcessStateToBatteryConsumerProcessState(int processState) {
+ switch (processState) {
+ case BatteryStats.Uid.PROCESS_STATE_TOP:
+ case BatteryStats.Uid.PROCESS_STATE_FOREGROUND:
+ return BatteryConsumer.PROCESS_STATE_FOREGROUND;
+ case BatteryStats.Uid.PROCESS_STATE_BACKGROUND:
+ case BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING:
+ return BatteryConsumer.PROCESS_STATE_BACKGROUND;
+ case BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE:
+ return BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+ case BatteryStats.Uid.PROCESS_STATE_CACHED:
+ return BatteryConsumer.PROCESS_STATE_CACHED;
+ default:
+ return BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Returns true if battery consumption is tracked on a per-process-state basis.
+ */
+ public abstract boolean isProcessStateDataAvailable();
+
+ /**
+ * The statistics associated with a particular uid.
+ */
+ public static abstract class Uid {
+
+ @UnsupportedAppUsage
+ public 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(maxTargetSdk = Build.VERSION_CODES.P)
+ public 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();
+
+ /**
+ * Returns the proportion of power consumed by the System Service
+ * calls made by this UID.
+ */
+ public abstract double getProportionalSystemServiceUsage();
+
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 active time of a UID while in the specified process state.
+ */
+ public abstract long getCpuActiveTime(int procState);
+
+ /**
+ * 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 boolean getCpuFreqTimes(@NonNull long[] timesInFreqMs, int procState);
+
+ /**
+ * Returns cpu times of an uid while the screen if off at a particular process state.
+ */
+ public abstract boolean getScreenOffCpuFreqTimes(@NonNull long[] timesInFreqMs,
+ 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;
+ /**
+ * State of the UID when it has no running processes. It is intentionally out of
+ * bounds 0..NUM_PROCESS_STATE.
+ */
+ public static final int PROCESS_STATE_NONEXISTENT = NUM_PROCESS_STATE;
+
+ // 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
+ };
+
+ 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", "faceDown", "deviceState"
+ };
+
+ 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);
+
+ /**
+ * Returns the amount of time (in microseconds) this UID was in the specified processState.
+ */
+ public abstract long getMobileRadioActiveTimeInProcessState(
+ @BatteryConsumer.ProcessState int processState);
+
+ 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);
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of bluetooth for this uid,
+ * derived from {@link android.hardware.power.stats.EnergyConsumerType#BLUETOOTH} bucket
+ * provided by the PowerStats service.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's bluetooth usage
+ * when in the specified process state.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothEnergyConsumptionUC(
+ @BatteryConsumer.ProcessState int processState);
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from
+ * derived from {@link android.hardware.power.stats.EnergyConsumerType#CPU} bucket
+ * provided by the PowerStats service.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCpuEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's cpu usage when in the
+ * specified process state.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCpuEnergyConsumptionUC(
+ @BatteryConsumer.ProcessState int processState);
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's GNSS usage, derived from
+ * derived from {@link android.hardware.power.stats.EnergyConsumerType#GNSS} bucket
+ * provided by the PowerStats service.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getGnssEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's radio usage, derived from
+ * derived from {@link android.hardware.power.stats.EnergyConsumerType#MOBILE_RADIO}
+ * bucket provided by the PowerStats service.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's radio usage when in the
+ * specified process state.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioEnergyConsumptionUC(
+ @BatteryConsumer.ProcessState int processState);
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the screen while on and uid active,
+ * derived from {@link android.hardware.power.stats.EnergyConsumerType#DISPLAY} bucket
+ * provided by the PowerStats service.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getScreenOnEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of wifi for this uid,
+ * derived from {@link android.hardware.power.stats.EnergyConsumerType#WIFI} bucket
+ * provided by the PowerStats service.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's wifi usage when in the
+ * specified process state.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiEnergyConsumptionUC(
+ @BatteryConsumer.ProcessState int processState);
+
+
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of UID's camera usage, derived from
+ * on-device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCameraEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) used by this uid for each
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * @return charge (in microcoulombs) consumed since last reset for each (custom) energy
+ * consumer of type OTHER, indexed by their ordinal. Returns null if no energy
+ * reporting is supported.
+ *
+ * {@hide}
+ */
+ public abstract @Nullable long[] getCustomEnergyConsumerBatteryConsumptionUC();
+
+ public static abstract class Sensor {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public 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 {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public Proc() {}
+
+ public static class ExcessivePower {
+
+ @UnsupportedAppUsage
+ public 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 {
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public 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();
+
+ /**
+ * Returns the total number of frequencies across all CPU clusters.
+ */
+ public abstract int getCpuFreqCount();
+
+ 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(@Nullable 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;
+
+ // Low power state stats
+ 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(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();
+ statSubsystemPowerState = in.readString();
+ }
+ }
+
+ /**
+ * Measured energy delta from the previous reading.
+ */
+ public static final class EnergyConsumerDetails {
+ /**
+ * Description of the energy consumer, such as CPU, DISPLAY etc
+ */
+ public static final class EnergyConsumer {
+ /**
+ * See android.hardware.power.stats.EnergyConsumerType
+ */
+ public int type;
+ /**
+ * Used when there are multipe energy consumers of the same type, such
+ * as CPU clusters, multiple displays on foldable devices etc.
+ */
+ public int ordinal;
+ /**
+ * Human-readable name of the energy consumer, e.g. "CPU"
+ */
+ public String name;
+ }
+ public EnergyConsumer[] consumers;
+ public long[] chargeUC;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < consumers.length; i++) {
+ if (chargeUC[i] == POWER_DATA_UNAVAILABLE) {
+ continue;
+ }
+ if (sb.length() != 0) {
+ sb.append(' ');
+ }
+ sb.append(consumers[i].name);
+ sb.append('=');
+ sb.append(chargeUC[i]);
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * CPU usage for a given UID.
+ */
+ public static final class CpuUsageDetails {
+ /**
+ * Descriptions of CPU power brackets, see PowerProfile.getCpuPowerBracketDescription
+ */
+ public String[] cpuBracketDescriptions;
+ public int uid;
+ /**
+ * The delta, in milliseconds, per CPU power bracket, from the previous record for the
+ * same UID.
+ */
+ public long[] cpuUsageMs;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ UserHandle.formatUid(sb, uid);
+ sb.append(": ");
+ for (int bracket = 0; bracket < cpuUsageMs.length; bracket++) {
+ if (bracket != 0) {
+ sb.append(", ");
+ }
+ sb.append(cpuUsageMs[bracket]);
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Battery history record.
+ */
+ public static final class HistoryItem {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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;
+ // Battery voltage in millivolts (mV).
+ @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 STATE2_EXTENSIONS_FLAG = 1 << 17;
+
+ 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;
+
+ // Non-null when there is energy consumer information
+ public EnergyConsumerDetails energyConsumerDetails;
+
+ // Non-null when there is CPU usage information
+ public CpuUsageDetails cpuUsageDetails;
+
+ 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 an alarm being sent out to an app.
+ 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 allowlist.
+ 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();
+
+ // Includes a tag's first occurrence in the parcel, so the value of the tag is written
+ // rather than just its index in the history tag pool.
+ public boolean tagsFirstOccurrence;
+
+ @UnsupportedAppUsage
+ public HistoryItem() {
+ }
+
+ public HistoryItem(Parcel src) {
+ readFromParcel(src);
+ }
+
+ 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();
+ time = src.readLong();
+ 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;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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;
+ tagsFirstOccurrence = false;
+ energyConsumerDetails = null;
+ cpuUsageDetails = null;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public void setTo(HistoryItem o) {
+ time = o.time;
+ cmd = o.cmd;
+ setToCommon(o);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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;
+ }
+ tagsFirstOccurrence = o.tagsFirstOccurrence;
+ currentTime = o.currentTime;
+ energyConsumerDetails = o.energyConsumerDetails;
+ cpuUsageDetails = o.cpuUsageDetails;
+ }
+
+ 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;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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();
+
+ public abstract int getHistoryStringPoolSize();
+
+ public abstract int getHistoryStringPoolBytes();
+
+ public abstract String getHistoryTagPoolString(int index);
+
+ public abstract int getHistoryTagPoolUid(int index);
+
+ /**
+ * Returns a BatteryStatsHistoryIterator. Battery history will continue being writable,
+ * but the iterator will continue iterating over the snapshot taken at the time this method
+ * is called.
+ */
+ public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();
+
+ /**
+ * 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 number of physical displays on the device.
+ *
+ * {@hide}
+ */
+ public abstract int getDisplayCount();
+
+ /**
+ * Returns the time in microseconds that the screen has been on for a display while the
+ * device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenOnTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been dozing while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been on with the given brightness
+ * level while the device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs);
+
+ /**
+ * 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}
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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_OUT_OF_SERVICE = 0;
+ public static final int DATA_CONNECTION_EMERGENCY_SERVICE =
+ TelephonyManager.getAllNetworkTypes().length + 1;
+ public static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
+
+
+ static final String[] DATA_CONNECTION_NAMES = {
+ "oos", "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",
+ "emngcy", "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);
+
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_",
+ value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE,
+ RADIO_ACCESS_TECHNOLOGY_NR})
+ public @interface RadioAccessTechnology {
+ }
+
+ /** @hide */
+ public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"};
+
+ /**
+ * Returns the time in milliseconds that the mobile radio has been active on a
+ * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+ * transmission power level.
+ *
+ * @param rat Radio Access Technology {@see RadioAccessTechnology}
+ * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+ * RADIO_ACCESS_TECHNOLOGY_NR. Use
+ * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+ * Technologies.
+ * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+ * @param elapsedRealtimeMs current elapsed realtime
+ * @return time (in milliseconds) the mobile radio spent active in the specified state,
+ * while on battery.
+ * @hide
+ */
+ public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs);
+
+ /**
+ * Returns the time in milliseconds that the mobile radio has been actively transmitting data on
+ * a given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+ * transmission power level.
+ *
+ * @param rat Radio Access Technology {@see RadioAccessTechnology}
+ * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+ * RADIO_ACCESS_TECHNOLOGY_NR. Use
+ * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+ * Technologies.
+ * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+ * @param elapsedRealtimeMs current elapsed realtime
+ * @return time (in milliseconds) the mobile radio spent actively transmitting data in the
+ * specified state, while on battery. Returns {@link DURATION_UNAVAILABLE} if
+ * data unavailable.
+ * @hide
+ */
+ public abstract long getActiveTxRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs);
+
+ /**
+ * Returns the time in milliseconds that the mobile radio has been actively receiving data on a
+ * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+ * transmission power level.
+ *
+ * @param rat Radio Access Technology {@see RadioAccessTechnology}
+ * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+ * RADIO_ACCESS_TECHNOLOGY_NR. Use
+ * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+ * Technologies.
+ * @param elapsedRealtimeMs current elapsed realtime
+ * @return time (in milliseconds) the mobile radio spent actively receiving data in the
+ * specified state, while on battery. Returns {@link DURATION_UNAVAILABLE} if
+ * data unavailable.
+ * @hide
+ */
+ public abstract long getActiveRxRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, long elapsedRealtimeMs);
+
+ 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"
+ };
+
+ /**
+ * Returned value if power data is unavailable.
+ *
+ * {@hide}
+ */
+ public static final long POWER_DATA_UNAVAILABLE = -1L;
+
+ /**
+ * Returned value if duration data is unavailable.
+ *
+ * {@hide}
+ */
+ public static final long DURATION_UNAVAILABLE = -1L;
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of bluetooth, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the cpu, derived from on device power
+ * measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCpuEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the GNSS, derived from on device power
+ * measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getGnssEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the radio, derived from on device power
+ * measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the phone calls, derived from on device
+ * power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getPhoneEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getScreenOnEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the screen in doze, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getScreenDozeEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of wifi, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of camera, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCameraEnergyConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) that each
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}) consumed.
+ *
+ * @return charge (in microcoulombs) used by each (custom) energy consumer of type OTHER,
+ * indexed by their ordinal. Returns null if no energy reporting is supported.
+ *
+ * {@hide}
+ */
+ public abstract @Nullable long[] getCustomEnergyConsumerBatteryConsumptionUC();
+
+ /**
+ * Returns the names of all {@link android.hardware.power.stats.EnergyConsumer}'s
+ * of (custom) energy consumer type
+ * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * {@hide}
+ */
+ public abstract @NonNull String[] getCustomEnergyConsumerNames();
+
+ 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",
+ new String[] { "none", "poor", "moderate", "good", "great" },
+ 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);
+
+ static final String[] WIFI_STATE_NAMES = {
+ "off", "scanning", "no_net", "disconn",
+ "sta", "p2p", "sta_p2p", "soft_ap"
+ };
+
+ /**
+ * Returns the time in microseconds that WiFi has been running in the given state.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiStateTime(@WifiState int wifiState,
+ long elapsedRealtimeUs, @StatName int which);
+
+ /**
+ * Returns the number of times that WiFi has entered the given state.
+ *
+ * {@hide}
+ */
+ public abstract int getWifiStateCount(@WifiState int wifiState, @StatName int which);
+
+ /**
+ * Returns the {@link Timer} object that tracks the given WiFi state.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiStateTimer(@WifiState int wifiState);
+
+ /**
+ * Returns the time in microseconds that the wifi supplicant has been
+ * in a given state.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiSupplStateTime(@WifiSupplState int state, long elapsedRealtimeUs,
+ @StatName int which);
+
+ /**
+ * Returns the number of times that the wifi supplicant has transitioned
+ * to a given state.
+ *
+ * {@hide}
+ */
+ public abstract int getWifiSupplStateCount(@WifiSupplState int state, @StatName int which);
+
+ /**
+ * Returns the {@link Timer} object that tracks the given wifi supplicant state.
+ *
+ * {@hide}
+ */
+ public abstract Timer getWifiSupplStateTimer(@WifiSupplState 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;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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 the timestamp of when battery stats collection started, in microseconds.
+ */
+ public abstract long getStatsStartRealtime();
+
+ /**
+ * 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 approximate CPU time (in microseconds) spent by the system server handling
+ * incoming service calls from apps. The result is returned as an array of longs,
+ * organized as a sequence like this:
+ * <pre>
+ * cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
+ * </pre>
+ *
+ * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
+ * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
+ */
+ @Nullable
+ public abstract long[] getSystemServiceTimeAtCpuSpeeds();
+
+ /**
+ * 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 elapsed 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 latest learned battery capacity in uAh.
+ */
+ public abstract int getLearnedBatteryCapacity();
+
+ /**
+ * 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 aggregated wake lock stats.
+ */
+ public abstract WakeLockStats getWakeLockStats();
+
+ /**
+ * Returns aggregated Bluetooth stats.
+ */
+ public abstract BluetoothBatteryStats getBluetoothBatteryStats();
+
+ /**
+ * 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();
+
+ 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();
+ }
+ }
+
+ /**
+ * Converts charge in mAh to string.
+ */
+ public static String formatCharge(double power) {
+ return formatValue(power);
+ }
+
+ /**
+ * Converts double to string, limiting small values to 3 significant figures.
+ */
+ private static String formatValue(double value) {
+ if (value == 0) return "0";
+
+ final String format;
+ if (value < .00001) {
+ format = "%.8f";
+ } else if (value < .0001) {
+ format = "%.7f";
+ } else if (value < .001) {
+ format = "%.6f";
+ } else if (value < .01) {
+ format = "%.5f";
+ } else if (value < .1) {
+ format = "%.4f";
+ } else if (value < 1) {
+ format = "%.3f";
+ } else if (value < 10) {
+ format = "%.2f";
+ } else if (value < 100) {
+ format = "%.1f";
+ } else {
+ format = "%.0f";
+ }
+
+ // Use English locale because this is never used in UI (only in checkin and dump).
+ return String.format(Locale.ENGLISH, format, value);
+ }
+
+ 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(
+ formatCharge(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());
+ }
+ }
+
+ private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix,
+ long rawRealtimeMs) {
+ final String allFrequenciesHeader =
+ " All frequencies:\n";
+ final String[] nrFrequencyRangeDescription = new String[]{
+ " Unknown frequency:\n",
+ " Low frequency (less than 1GHz):\n",
+ " Middle frequency (1GHz to 3GHz):\n",
+ " High frequency (3GHz to 6GHz):\n",
+ " Mmwave frequency (greater than 6GHz):\n"};
+ final String signalStrengthHeader =
+ " Signal Strength Time:\n";
+ final String txHeader =
+ " Tx Time:\n";
+ final String rxHeader =
+ " Rx Time: ";
+ final String[] signalStrengthDescription = new String[]{
+ " unknown: ",
+ " poor: ",
+ " moderate: ",
+ " good: ",
+ " great: "};
+
+ final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000,
+ STATS_SINCE_CHARGED) / 1000;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("Active Cellular Radio Access Technology Breakdown:");
+ pw.println(sb);
+
+ boolean hasData = false;
+ final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels();
+ for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(":\n");
+ sb.append(prefix);
+
+ final int numFreqLvl =
+ rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1;
+ for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) {
+ final int freqDescriptionStart = sb.length();
+ boolean hasFreqData = false;
+ if (rat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ sb.append(nrFrequencyRangeDescription[freqLvl]);
+ } else {
+ sb.append(allFrequenciesHeader);
+ }
+
+ sb.append(prefix);
+ sb.append(signalStrengthHeader);
+ for (int strength = 0; strength < numSignalStrength; strength++) {
+ final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength,
+ rawRealtimeMs);
+ if (timeMs <= 0) continue;
+ hasFreqData = true;
+ sb.append(prefix);
+ sb.append(signalStrengthDescription[strength]);
+ formatTimeMs(sb, timeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+ sb.append(")\n");
+ }
+
+ sb.append(prefix);
+ sb.append(txHeader);
+ for (int strength = 0; strength < numSignalStrength; strength++) {
+ final long timeMs = getActiveTxRadioDurationMs(rat, freqLvl, strength,
+ rawRealtimeMs);
+ if (timeMs <= 0) continue;
+ hasFreqData = true;
+ sb.append(prefix);
+ sb.append(signalStrengthDescription[strength]);
+ formatTimeMs(sb, timeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+ sb.append(")\n");
+ }
+
+ sb.append(prefix);
+ sb.append(rxHeader);
+ final long rxTimeMs = getActiveRxRadioDurationMs(rat, freqLvl, rawRealtimeMs);
+ formatTimeMs(sb, rxTimeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(rxTimeMs, totalActiveTimesMs));
+ sb.append(")\n");
+
+ if (hasFreqData) {
+ hasData = true;
+ pw.print(sb);
+ sb.setLength(0);
+ sb.append(prefix);
+ } else {
+ // No useful data was printed, rewind sb to before the start of this frequency.
+ sb.setLength(freqDescriptionStart);
+ }
+ }
+ }
+
+ if (!hasData) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" (no activity)");
+ pw.println(sb);
+ }
+ }
+
+ private static final String[] CHECKIN_POWER_COMPONENT_LABELS =
+ new String[BatteryConsumer.POWER_COMPONENT_COUNT];
+ static {
+ // Assign individually to avoid future mismatch of indices
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_SCREEN] = "scrn";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_CPU] = "cpu";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_BLUETOOTH] = "blue";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_CAMERA] = "camera";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_AUDIO] = "audio";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_VIDEO] = "video";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_FLASHLIGHT] = "flashlight";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO] = "cell";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_SENSORS] = "sensors";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_GNSS] = "gnss";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_WIFI] = "wifi";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_MEMORY] = "memory";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_PHONE] = "phone";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY] = "ambi";
+ CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_IDLE] = "idle";
+ }
+
+ /**
+ * 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[CellSignalStrength.getNumSignalStrengthLevels()];
+ for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); 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 < CellSignalStrength.getNumSignalStrengthLevels(); 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 BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */);
+ dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA,
+ formatCharge(stats.getBatteryCapacity()),
+ formatCharge(stats.getConsumedPower()),
+ formatCharge(stats.getDischargedPowerRange().getLower()),
+ formatCharge(stats.getDischargedPowerRange().getUpper()));
+ final BatteryConsumer deviceConsumer = stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ for (@BatteryConsumer.PowerComponent int powerComponent = 0;
+ powerComponent < BatteryConsumer.POWER_COMPONENT_COUNT; powerComponent++) {
+ String label = CHECKIN_POWER_COMPONENT_LABELS[powerComponent];
+ if (label == null) {
+ label = "???";
+ }
+ dumpLine(pw, 0 /* uid */, category, POWER_USE_ITEM_DATA, label,
+ formatCharge(deviceConsumer.getConsumedPower(powerComponent)),
+ shouldHidePowerComponent(powerComponent) ? 1 : 0, "0", "0");
+ }
+
+ final ProportionalAttributionCalculator proportionalAttributionCalculator =
+ new ProportionalAttributionCalculator(context, stats);
+ final List<UidBatteryConsumer> uidBatteryConsumers = stats.getUidBatteryConsumers();
+ for (int i = 0; i < uidBatteryConsumers.size(); i++) {
+ UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+ dumpLine(pw, consumer.getUid(), category, POWER_USE_ITEM_DATA, "uid",
+ formatCharge(consumer.getConsumedPower()),
+ proportionalAttributionCalculator.isSystemBatteryConsumer(consumer) ? 1 : 0,
+ formatCharge(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)),
+ formatCharge(
+ proportionalAttributionCalculator.getProportionalPowerMah(consumer)));
+ }
+
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ sb.setLength(0);
+ for (int i = 0; i < cpuFreqs.length; ++i) {
+ if (i != 0) sb.append(',');
+ sb.append(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 int[] jobStopReasonCodes = JobParameters.getJobStopReasonCodes();
+ final Object[] jobCompletionArgs = new Object[jobStopReasonCodes.length + 1];
+
+ final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+ for (int ic=completions.size()-1; ic>=0; ic--) {
+ SparseIntArray types = completions.valueAt(ic);
+ if (types != null) {
+ jobCompletionArgs[0] = "\"" + completions.keyAt(ic) + "\"";
+ for (int i = 0; i < jobStopReasonCodes.length; i++) {
+ jobCompletionArgs[i + 1] = types.get(jobStopReasonCodes[i], 0);
+ }
+
+ dumpLine(pw, uid, category, JOB_COMPLETION_DATA, jobCompletionArgs);
+ }
+ }
+
+ // 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) {
+ if (i != 0) sb.append(',');
+ sb.append(cpuFreqTimeMs[i]);
+ }
+ final long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+ if (screenOffCpuFreqTimeMs != null) {
+ for (int i = 0; i < screenOffCpuFreqTimeMs.length; ++i) {
+ sb.append(',').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());
+ }
+
+ final long[] timesInFreqMs = new long[getCpuFreqCount()];
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
+ sb.setLength(0);
+ for (int i = 0; i < timesInFreqMs.length; ++i) {
+ if (i != 0) sb.append(',');
+ sb.append(timesInFreqMs[i]);
+ }
+ if (u.getScreenOffCpuFreqTimes(timesInFreqMs, procState)) {
+ for (int i = 0; i < timesInFreqMs.length; ++i) {
+ sb.append(',').append(timesInFreqMs[i]);
+ }
+ } else {
+ for (int i = 0; i < timesInFreqMs.length; ++i) {
+ sb.append(",0");
+ }
+ }
+ dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+ Uid.UID_PROCESS_TYPES[procState], timesInFreqMs.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(formatCharge(power));
+ }
+
+ private void printmAh(StringBuilder sb, double power) {
+ sb.append(formatCharge(power));
+ }
+
+ @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(formatCharge(estimatedBatteryCapacity));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
+ final int lastLearnedBatteryCapacity = getLearnedBatteryCapacity();
+ if (lastLearnedBatteryCapacity > 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Last learned battery capacity: ");
+ sb.append(formatCharge(lastLearnedBatteryCapacity / 1000));
+ 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(formatCharge(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(formatCharge(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(formatCharge(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(formatCharge(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(formatCharge(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(formatCharge(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(formatCharge(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(formatCharge(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 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());
+ }
+
+ final int numDisplays = getDisplayCount();
+ if (numDisplays > 1) {
+ pw.println("");
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY START");
+ pw.println(sb.toString());
+
+ for (int display = 0; display < numDisplays; display++) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Display ");
+ sb.append(display);
+ sb.append(" Statistics:");
+ pw.println(sb.toString());
+
+ final long displayScreenOnTime = getDisplayScreenOnTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen on: ");
+ formatTimeMs(sb, displayScreenOnTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenOnTime, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(" Screen brightness levels:");
+ didOne = false;
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ final long timeUs = getDisplayScreenBrightnessTime(display, bin, rawRealtime);
+ if (timeUs == 0) {
+ continue;
+ }
+ didOne = true;
+ sb.append("\n ");
+ sb.append(prefix);
+ sb.append(SCREEN_BRIGHTNESS_NAMES[bin]);
+ sb.append(" ");
+ formatTimeMs(sb, timeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeUs, displayScreenOnTime));
+ sb.append(")");
+ }
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
+
+ final long displayScreenDozeTimeUs = getDisplayScreenDozeTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen Doze: ");
+ formatTimeMs(sb, displayScreenDozeTimeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenDozeTimeUs, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+ }
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY END");
+ 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);
+
+ printCellularPerRatBreakdown(pw, sb, prefix + " ", rawRealtimeMs);
+
+ 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(CellSignalStrength.getNumSignalStrengthLevels(),
+ 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(
+ GnssSignalQuality.NUM_GNSS_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();
+
+
+ BatteryUsageStats stats = getBatteryUsageStats(context, true /* detailed */);
+ stats.dump(pw, prefix);
+
+ List<UidMobileRadioStats> uidMobileRadioStats =
+ getUidMobileRadioStats(stats.getUidBatteryConsumers());
+ if (uidMobileRadioStats.size() > 0) {
+ pw.print(prefix);
+ pw.println(" Per-app mobile ms per packet:");
+ long totalTime = 0;
+ for (int i = 0; i < uidMobileRadioStats.size(); i++) {
+ final UidMobileRadioStats mrs = uidMobileRadioStats.get(i);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Uid ");
+ UserHandle.formatUid(sb, mrs.uid);
+ sb.append(": ");
+ sb.append(formatValue(mrs.millisecondsPerPacket));
+ sb.append(" (");
+ sb.append(mrs.rxPackets + mrs.txPackets);
+ sb.append(" packets over ");
+ formatTimeMsNoSpace(sb, mrs.radioActiveMs);
+ sb.append(") ");
+ sb.append(mrs.radioActiveCount);
+ sb.append("x");
+ pw.println(sb);
+ totalTime += mrs.radioActiveMs;
+ }
+ 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);
+ 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 Timer> kernelWakelocks
+ = getKernelWakelockStats();
+ if (kernelWakelocks.size() > 0) {
+ final ArrayList<TimerEntry> ktimers = new ArrayList<>();
+ for (Map.Entry<String, ? extends Timer> ent
+ : kernelWakelocks.entrySet()) {
+ final 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(' ').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(formatCharge(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 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.getInternalReasonCodeDescription(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 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(' ').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(' ').append(screenOffCpuFreqTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+
+ final long[] timesInFreqMs = new long[getCpuFreqCount()];
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
+ sb.setLength(0);
+ sb.append(" Cpu times per freq at state ")
+ .append(Uid.PROCESS_STATE_NAMES[procState]).append(':');
+ for (int i = 0; i < timesInFreqMs.length; ++i) {
+ sb.append(" ").append(timesInFreqMs[i]);
+ }
+ pw.println(sb.toString());
+ }
+
+ if (u.getScreenOffCpuFreqTimes(timesInFreqMs, procState)) {
+ sb.setLength(0);
+ sb.append(" Screen-off cpu times per freq at state ")
+ .append(Uid.PROCESS_STATE_NAMES[procState]).append(':');
+ for (int i = 0; i < timesInFreqMs.length; ++i) {
+ sb.append(" ").append(timesInFreqMs[i]);
+ }
+ pw.println(sb.toString());
+ }
+ }
+
+ final ArrayMap<String, ? extends 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 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 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 (rec.cpuUsageDetails != null
+ && rec.cpuUsageDetails.cpuBracketDescriptions != null
+ && checkin) {
+ String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+ for (int bracket = 0; bracket < descriptions.length; bracket++) {
+ item.append(BATTERY_STATS_CHECKIN_VERSION);
+ item.append(',');
+ item.append(HISTORY_DATA);
+ item.append(",0,XB,");
+ item.append(descriptions.length);
+ item.append(',');
+ item.append(bracket);
+ item.append(',');
+ item.append(descriptions[bracket]);
+ item.append("\n");
+ }
+ }
+
+ 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("\"");
+ }
+ }
+ boolean firstExtension = true;
+ if (rec.energyConsumerDetails != null) {
+ firstExtension = false;
+ if (!checkin) {
+ item.append(" ext=energy:");
+ item.append(rec.energyConsumerDetails);
+ } else {
+ item.append(",XE");
+ for (int i = 0; i < rec.energyConsumerDetails.consumers.length; i++) {
+ if (rec.energyConsumerDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
+ item.append(',');
+ item.append(rec.energyConsumerDetails.consumers[i].name);
+ item.append('=');
+ item.append(rec.energyConsumerDetails.chargeUC[i]);
+ }
+ }
+ }
+ }
+ if (rec.cpuUsageDetails != null) {
+ if (!checkin) {
+ if (!firstExtension) {
+ item.append("\n ");
+ }
+ String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+ if (descriptions != null) {
+ for (int bracket = 0; bracket < descriptions.length; bracket++) {
+ item.append(" ext=cpu-bracket:");
+ item.append(bracket);
+ item.append(":");
+ item.append(descriptions[bracket]);
+ item.append("\n ");
+ }
+ }
+ item.append(" ext=cpu:");
+ item.append(rec.cpuUsageDetails);
+ } else {
+ if (!firstExtension) {
+ item.append('\n');
+ item.append(BATTERY_STATS_CHECKIN_VERSION);
+ item.append(',');
+ item.append(HISTORY_DATA);
+ item.append(",0");
+ }
+ item.append(",XC,");
+ item.append(rec.cpuUsageDetails.uid);
+ for (int i = 0; i < rec.cpuUsageDetails.cpuUsageMs.length; i++) {
+ item.append(',');
+ item.append(rec.cpuUsageDetails.cpuUsageMs[i]);
+ }
+ }
+ firstExtension = false;
+ }
+ 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(", 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.statSubsystemPowerState != null) {
+ item.append(rec.stepDetails.statSubsystemPowerState);
+ }
+ item.append("\n");
+ }
+ }
+ oldState = rec.states;
+ oldState2 = rec.states2;
+ // Clear High Tx Power Flag for volta positioning
+ if ((rec.states2 & HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG) != 0) {
+ rec.states2 &= ~HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
+ }
+ }
+ 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 dumpHistory(PrintWriter pw, int flags, long histStart, boolean checkin) {
+ synchronized (this) {
+ dumpHistoryTagPoolLocked(pw, checkin);
+ }
+
+ final HistoryPrinter hprinter = new HistoryPrinter();
+ long lastTime = -1;
+ long baseTime = -1;
+ boolean printed = false;
+ HistoryEventTracker tracker = null;
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ HistoryItem rec;
+ while ((rec = iterator.next()) != null) {
+ try {
+ 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++) {
+ Map<String, SparseIntArray> active =
+ tracker.getStateForEvent(i);
+ if (active == null) {
+ continue;
+ }
+ for (Map.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);
+ }
+ } catch (Throwable t) {
+ t.printStackTrace(pw);
+ Slog.wtf(TAG, "Corrupted battery history", t);
+ break;
+ }
+ }
+ }
+ if (histStart >= 0) {
+ commitCurrentHistoryBatchLocked();
+ pw.print(checkin ? "NEXT: " : " NEXT: "); pw.println(lastTime+1);
+ }
+ }
+
+ private void dumpHistoryTagPoolLocked(PrintWriter pw, boolean checkin) {
+ if (checkin) {
+ 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);
+ if (str != null) {
+ str = str.replace("\\", "\\\\");
+ str = str.replace("\"", "\\\"");
+ pw.print(str);
+ }
+ pw.print("\"");
+ pw.println();
+ }
+ } else {
+ final long historyTotalSize = getHistoryTotalSize();
+ final long historyUsedSize = getHistoryUsedSize();
+ 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("):");
+ }
+ }
+
+ 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 dump(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
+ synchronized (this) {
+ prepareForDumpLocked();
+ }
+
+ final boolean filtering = (flags
+ & (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
+
+ if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
+ dumpHistory(pw, flags, histStart, false);
+ pw.println();
+ }
+
+ if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
+ return;
+ }
+
+ synchronized (this) {
+ dumpLocked(context, pw, flags, reqUid, filtering);
+ }
+ }
+
+ private void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid,
+ boolean filtering) {
+ 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 dumpCheckin(Context context, PrintWriter pw,
+ List<ApplicationInfo> apps, int flags, long histStart) {
+ synchronized (this) {
+ prepareForDumpLocked();
+
+ dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA,
+ CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(),
+ getEndPlatformVersion());
+ }
+
+ if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+ dumpHistory(pw, flags, histStart, true);
+ }
+
+ if ((flags & DUMP_HISTORY_ONLY) != 0) {
+ return;
+ }
+
+ synchronized (this) {
+ dumpCheckinLocked(context, pw, apps, flags);
+ }
+ }
+
+ private void dumpCheckinLocked(Context context, PrintWriter pw, List<ApplicationInfo> apps,
+ int flags) {
+ 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 BatteryUsageStats stats = getBatteryUsageStats(context, false /* detailed */);
+ ProportionalAttributionCalculator proportionalAttributionCalculator =
+ new ProportionalAttributionCalculator(context, stats);
+ dumpProtoAppsLocked(proto, stats, apps, proportionalAttributionCalculator);
+ dumpProtoSystemLocked(proto, stats);
+ }
+
+ proto.end(bToken);
+ proto.flush();
+ }
+
+ private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryUsageStats stats,
+ List<ApplicationInfo> apps,
+ ProportionalAttributionCalculator proportionalAttributionCalculator) {
+ 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<UidBatteryConsumer> uidToConsumer = new SparseArray<>();
+ final List<UidBatteryConsumer> consumers = stats.getUidBatteryConsumers();
+ for (int i = consumers.size() - 1; i >= 0; --i) {
+ final UidBatteryConsumer bs = consumers.get(i);
+ uidToConsumer.put(bs.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);
+ }
+ }
+ }
+
+ final long[] timesInFreqMs = new long[getCpuFreqCount()];
+ final long[] timesInFreqScreenOffMs = new long[getCpuFreqCount()];
+ for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+ if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
+ if (!u.getScreenOffCpuFreqTimes(timesInFreqScreenOffMs, procState)) {
+ Arrays.fill(timesInFreqScreenOffMs, 0);
+ }
+ final long procToken = proto.start(UidProto.Cpu.BY_PROCESS_STATE);
+ proto.write(UidProto.Cpu.ByProcessState.PROCESS_STATE, procState);
+ for (int ic = 0; ic < timesInFreqMs.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,
+ timesInFreqMs[ic]);
+ proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+ timesInFreqScreenOffMs[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();
+ 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 : JobParameters.getJobStopReasonCodes()) {
+ 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)
+ UidBatteryConsumer consumer = uidToConsumer.get(uid);
+ if (consumer != null) {
+ final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+ proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, consumer.getConsumedPower());
+ proto.write(UidProto.PowerUseItem.SHOULD_HIDE,
+ proportionalAttributionCalculator.isSystemBatteryConsumer(consumer));
+ proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH,
+ consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN));
+ proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+ proportionalAttributionCalculator.getProportionalPowerMah(consumer));
+ 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) {
+ 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());
+ 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();
+ long lastTime = -1;
+ long baseTime = -1;
+ boolean printed = false;
+ HistoryEventTracker tracker = null;
+ try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+ HistoryItem rec;
+ while ((rec = iterator.next()) != null) {
+ 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));
+ }
+ }
+ }
+
+ private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryUsageStats stats) {
+ 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_OUT_OF_SERVICE);
+ int telephonyNetworkType = i;
+ if (i == DATA_CONNECTION_OTHER || i == DATA_CONNECTION_EMERGENCY_SERVICE) {
+ 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);
+
+ final BatteryConsumer deviceConsumer = stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+ for (int powerComponent = 0; powerComponent < BatteryConsumer.POWER_COMPONENT_COUNT;
+ powerComponent++) {
+ int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+ switch (powerComponent) {
+ case BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY:
+ n = SystemProto.PowerUseItem.AMBIENT_DISPLAY;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_IDLE:
+ n = SystemProto.PowerUseItem.IDLE;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO:
+ n = SystemProto.PowerUseItem.CELL;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_PHONE:
+ n = SystemProto.PowerUseItem.PHONE;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_WIFI:
+ n = SystemProto.PowerUseItem.WIFI;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_BLUETOOTH:
+ n = SystemProto.PowerUseItem.BLUETOOTH;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_SCREEN:
+ n = SystemProto.PowerUseItem.SCREEN;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_FLASHLIGHT:
+ n = SystemProto.PowerUseItem.FLASHLIGHT;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_CAMERA:
+ n = SystemProto.PowerUseItem.CAMERA;
+ break;
+ case BatteryConsumer.POWER_COMPONENT_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, 0);
+ proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH,
+ deviceConsumer.getConsumedPower(powerComponent));
+ proto.write(SystemProto.PowerUseItem.SHOULD_HIDE,
+ shouldHidePowerComponent(powerComponent));
+ proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, 0);
+ proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH, 0);
+ 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,
+ stats.getBatteryCapacity());
+ proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, stats.getConsumedPower());
+ proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH,
+ stats.getDischargedPowerRange().getLower());
+ proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH,
+ stats.getDischargedPowerRange().getUpper());
+ 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 < CellSignalStrength.getNumSignalStrengthLevels(); ++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);
+ }
+
+ /**
+ * Returns true if the device does not have data-capable telephony.
+ */
+ public static boolean checkWifiOnly(Context context) {
+ final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ if (tm == null) {
+ return false;
+ }
+ return !tm.isDataCapable();
+ }
+
+ protected abstract BatteryUsageStats getBatteryUsageStats(Context context, boolean detailed);
+
+ private boolean shouldHidePowerComponent(int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE
+ || powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO
+ || powerComponent == BatteryConsumer.POWER_COMPONENT_SCREEN
+ || powerComponent == BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY;
+ }
+
+ private static class ProportionalAttributionCalculator {
+ private static final double SYSTEM_BATTERY_CONSUMER = -1;
+ private final PackageManager mPackageManager;
+ private final HashSet<String> mSystemAndServicePackages;
+ private final SparseDoubleArray mProportionalPowerMah;
+
+ ProportionalAttributionCalculator(Context context, BatteryUsageStats stats) {
+ mPackageManager = context.getPackageManager();
+ final Resources resources = context.getResources();
+ final String[] systemPackageArray = resources.getStringArray(
+ com.android.internal.R.array.config_batteryPackageTypeSystem);
+ final String[] servicePackageArray = resources.getStringArray(
+ com.android.internal.R.array.config_batteryPackageTypeService);
+ mSystemAndServicePackages =
+ new HashSet<>(systemPackageArray.length + servicePackageArray.length);
+ for (String packageName : systemPackageArray) {
+ mSystemAndServicePackages.add(packageName);
+ }
+ for (String packageName : servicePackageArray) {
+ mSystemAndServicePackages.add(packageName);
+ }
+
+ final List<UidBatteryConsumer> uidBatteryConsumers = stats.getUidBatteryConsumers();
+ mProportionalPowerMah = new SparseDoubleArray(uidBatteryConsumers.size());
+ double systemPowerMah = 0;
+ for (int i = uidBatteryConsumers.size() - 1; i >= 0; i--) {
+ UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+ final int uid = consumer.getUid();
+ if (isSystemUid(uid)) {
+ mProportionalPowerMah.put(uid, SYSTEM_BATTERY_CONSUMER);
+ systemPowerMah += consumer.getConsumedPower();
+ }
+ }
+
+ final double totalRemainingPower = stats.getConsumedPower() - systemPowerMah;
+ if (Math.abs(totalRemainingPower) > 1e-3) {
+ for (int i = uidBatteryConsumers.size() - 1; i >= 0; i--) {
+ UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+ final int uid = consumer.getUid();
+ if (mProportionalPowerMah.get(uid) != SYSTEM_BATTERY_CONSUMER) {
+ final double power = consumer.getConsumedPower();
+ mProportionalPowerMah.put(uid,
+ power + systemPowerMah * power / totalRemainingPower);
+ }
+ }
+ }
+ }
+
+ boolean isSystemBatteryConsumer(UidBatteryConsumer consumer) {
+ return mProportionalPowerMah.get(consumer.getUid()) < 0;
+ }
+
+ double getProportionalPowerMah(UidBatteryConsumer consumer) {
+ final double powerMah = mProportionalPowerMah.get(consumer.getUid());
+ return powerMah >= 0 ? powerMah : 0;
+ }
+
+ /**
+ * Check whether the UID is one of the system UIDs or a service UID
+ */
+ private boolean isSystemUid(int uid) {
+ if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
+ return true;
+ }
+
+ final String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null) {
+ return false;
+ }
+
+ for (String packageName : packages) {
+ if (mSystemAndServicePackages.contains(packageName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private static class UidMobileRadioStats {
+ public final int uid;
+ public final long rxPackets;
+ public final long txPackets;
+ public final long radioActiveMs;
+ public final int radioActiveCount;
+ public final double millisecondsPerPacket;
+
+ private UidMobileRadioStats(int uid, long rxPackets, long txPackets, long radioActiveMs,
+ int radioActiveCount, double millisecondsPerPacket) {
+ this.uid = uid;
+ this.txPackets = txPackets;
+ this.rxPackets = rxPackets;
+ this.radioActiveMs = radioActiveMs;
+ this.radioActiveCount = radioActiveCount;
+ this.millisecondsPerPacket = millisecondsPerPacket;
+ }
+ }
+
+ private List<UidMobileRadioStats> getUidMobileRadioStats(
+ List<UidBatteryConsumer> uidBatteryConsumers) {
+ final SparseArray<? extends Uid> uidStats = getUidStats();
+ List<UidMobileRadioStats> uidMobileRadioStats = Lists.newArrayList();
+ for (int i = 0; i < uidBatteryConsumers.size(); i++) {
+ final UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+ if (consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) == 0) {
+ continue;
+ }
+
+ final int uid = consumer.getUid();
+ final Uid u = uidStats.get(uid);
+ final long rxPackets = u.getNetworkActivityPackets(
+ BatteryStats.NETWORK_MOBILE_RX_DATA, STATS_SINCE_CHARGED);
+ final long txPackets = u.getNetworkActivityPackets(
+ BatteryStats.NETWORK_MOBILE_TX_DATA, STATS_SINCE_CHARGED);
+ if (rxPackets == 0 && txPackets == 0) {
+ continue;
+ }
+ final long radioActiveMs = u.getMobileRadioActiveTime(STATS_SINCE_CHARGED) / 1000;
+ final int radioActiveCount = u.getMobileRadioActiveCount(STATS_SINCE_CHARGED);
+ final double msPerPacket = (double) radioActiveMs / (rxPackets + txPackets);
+ if (msPerPacket == 0) {
+ continue;
+ }
+ uidMobileRadioStats.add(
+ new UidMobileRadioStats(uid, rxPackets, txPackets, radioActiveMs,
+ radioActiveCount, msPerPacket));
+ }
+ uidMobileRadioStats.sort(
+ (lhs, rhs) -> Double.compare(rhs.millisecondsPerPacket, lhs.millisecondsPerPacket));
+ return uidMobileRadioStats;
+ }
+}
diff --git a/android-34/android/os/BatteryStatsInternal.java b/android-34/android/os/BatteryStatsInternal.java
new file mode 100644
index 0000000..0713999
--- /dev/null
+++ b/android-34/android/os/BatteryStatsInternal.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.net.Network;
+
+import com.android.internal.os.BinderCallsStats;
+import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 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 {
+
+ public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1;
+ public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1;
+ public static final int CPU_WAKEUP_SUBSYSTEM_WIFI = 2;
+ public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3;
+ public static final int CPU_WAKEUP_SUBSYSTEM_SENSOR = 4;
+ public static final int CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA = 5;
+
+ /** @hide */
+ @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
+ CPU_WAKEUP_SUBSYSTEM_UNKNOWN,
+ CPU_WAKEUP_SUBSYSTEM_ALARM,
+ CPU_WAKEUP_SUBSYSTEM_WIFI,
+ CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+ CPU_WAKEUP_SUBSYSTEM_SENSOR,
+ CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CpuWakeupSubsystem {
+ }
+
+ /**
+ * Returns the wifi interfaces.
+ */
+ public abstract String[] getWifiIfaces();
+
+ /**
+ * Returns the mobile data interfaces.
+ */
+ public abstract String[] getMobileIfaces();
+
+ /** Returns CPU times for system server thread groups. */
+ public abstract SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes();
+
+ /**
+ * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+ * and per-UID basis.
+ *
+ * <p>
+ * Note: This is a slow running method and should be called from non-blocking threads only.
+ * </p>
+ */
+ public abstract List<BatteryUsageStats> getBatteryUsageStats(
+ List<BatteryUsageStatsQuery> queries);
+
+ /**
+ * 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);
+
+ /**
+ * Informs battery stats of a data packet that woke up the CPU.
+ *
+ * @param network The network over which the packet arrived.
+ * @param elapsedMillis The time of the packet's arrival in elapsed timebase.
+ * @param uid The uid that received the packet.
+ */
+ public abstract void noteCpuWakingNetworkPacket(Network network, long elapsedMillis, int uid);
+
+ /**
+ * Informs battery stats of binder stats for the given work source UID.
+ */
+ public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount,
+ Collection<BinderCallsStats.CallStat> callStats);
+
+ /**
+ * Informs battery stats of native thread IDs of threads taking incoming binder calls.
+ */
+ public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
+
+ /**
+ * Reports a sound trigger recognition event that may have woken up the CPU.
+ * @param elapsedMillis The time when the event happened in the elapsed timebase.
+ * @param uid The uid that requested this trigger.
+ */
+ public abstract void noteWakingSoundTrigger(long elapsedMillis, int uid);
+
+ /**
+ * Reports an alarm batch that would have woken up the CPU.
+ * @param elapsedMillis The time at which this alarm batch was scheduled to go off.
+ * @param uids the uids of all apps that have any alarm in this batch.
+ */
+ public abstract void noteWakingAlarmBatch(long elapsedMillis, int... uids);
+}
diff --git a/android-34/android/os/BatteryStatsManager.java b/android-34/android/os/BatteryStatsManager.java
new file mode 100644
index 0000000..955fad3
--- /dev/null
+++ b/android-34/android/os/BatteryStatsManager.java
@@ -0,0 +1,675 @@
+/*
+ * 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.IntRange;
+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.NetworkStack;
+import android.os.connectivity.CellularBatteryStats;
+import android.os.connectivity.WifiBatteryStats;
+import android.telephony.DataConnectionRealTimeInfo;
+
+import com.android.internal.app.IBatteryStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * This class provides an API surface for internal system components to report events that are
+ * needed for battery usage/estimation and battery blaming for apps.
+ *
+ * Note: This internally uses the same {@link IBatteryStats} binder service as the public
+ * {@link BatteryManager}.
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.BATTERY_STATS_SERVICE)
+public final class BatteryStatsManager {
+ /**
+ * Wifi states.
+ *
+ * @see #noteWifiState(int, String)
+ */
+ /**
+ * Wifi fully off.
+ */
+ public static final int WIFI_STATE_OFF = 0;
+ /**
+ * Wifi connectivity off, but scanning enabled.
+ */
+ public static final int WIFI_STATE_OFF_SCANNING = 1;
+ /**
+ * Wifi on, but no saved infrastructure networks to connect to.
+ */
+ public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
+ /**
+ * Wifi on, but not connected to any infrastructure networks.
+ */
+ public static final int WIFI_STATE_ON_DISCONNECTED = 3;
+ /**
+ * Wifi on and connected to a infrastructure network.
+ */
+ public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
+ /**
+ * Wifi on and connected to a P2P device, but no infrastructure connection to a network.
+ */
+ public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
+ /**
+ * Wifi on and connected to both a P2P device and infrastructure connection to a network.
+ */
+ public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
+ /**
+ * SoftAp/Hotspot turned on.
+ */
+ public static final int WIFI_STATE_SOFT_AP = 7;
+
+ /** @hide */
+ public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP + 1;
+
+ /** @hide */
+ @IntDef(prefix = { "WIFI_STATE_" }, value = {
+ WIFI_STATE_OFF,
+ WIFI_STATE_OFF_SCANNING,
+ WIFI_STATE_ON_NO_NETWORKS,
+ WIFI_STATE_ON_DISCONNECTED,
+ WIFI_STATE_ON_CONNECTED_STA,
+ WIFI_STATE_ON_CONNECTED_P2P,
+ WIFI_STATE_ON_CONNECTED_STA_P2P,
+ WIFI_STATE_SOFT_AP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiState {}
+
+ /**
+ * Wifi supplicant daemon states.
+ *
+ * @see android.net.wifi.SupplicantState for detailed description of states.
+ * @see #noteWifiSupplicantStateChanged(int)
+ */
+ /** @see android.net.wifi.SupplicantState#INVALID */
+ public static final int WIFI_SUPPL_STATE_INVALID = 0;
+ /** @see android.net.wifi.SupplicantState#DISCONNECTED*/
+ public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
+ /** @see android.net.wifi.SupplicantState#INTERFACE_DISABLED */
+ public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
+ /** @see android.net.wifi.SupplicantState#INACTIVE*/
+ public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
+ /** @see android.net.wifi.SupplicantState#SCANNING*/
+ public static final int WIFI_SUPPL_STATE_SCANNING = 4;
+ /** @see android.net.wifi.SupplicantState#AUTHENTICATING */
+ public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
+ /** @see android.net.wifi.SupplicantState#ASSOCIATING */
+ public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
+ /** @see android.net.wifi.SupplicantState#ASSOCIATED */
+ public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
+ /** @see android.net.wifi.SupplicantState#FOUR_WAY_HANDSHAKE */
+ public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
+ /** @see android.net.wifi.SupplicantState#GROUP_HANDSHAKE */
+ public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
+ /** @see android.net.wifi.SupplicantState#COMPLETED */
+ public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
+ /** @see android.net.wifi.SupplicantState#DORMANT */
+ public static final int WIFI_SUPPL_STATE_DORMANT = 11;
+ /** @see android.net.wifi.SupplicantState#UNINITIALIZED */
+ public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
+
+ /** @hide */
+ public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED + 1;
+
+ /** @hide */
+ @IntDef(prefix = { "WIFI_SUPPL_STATE_" }, value = {
+ WIFI_SUPPL_STATE_INVALID,
+ WIFI_SUPPL_STATE_DISCONNECTED,
+ WIFI_SUPPL_STATE_INTERFACE_DISABLED,
+ WIFI_SUPPL_STATE_INACTIVE,
+ WIFI_SUPPL_STATE_SCANNING,
+ WIFI_SUPPL_STATE_AUTHENTICATING,
+ WIFI_SUPPL_STATE_ASSOCIATING,
+ WIFI_SUPPL_STATE_ASSOCIATED,
+ WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE,
+ WIFI_SUPPL_STATE_GROUP_HANDSHAKE,
+ WIFI_SUPPL_STATE_COMPLETED,
+ WIFI_SUPPL_STATE_DORMANT,
+ WIFI_SUPPL_STATE_UNINITIALIZED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiSupplState {}
+
+
+ private final IBatteryStats mBatteryStats;
+
+ /** @hide */
+ public BatteryStatsManager(IBatteryStats batteryStats) {
+ mBatteryStats = batteryStats;
+ }
+
+
+ /**
+ * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+ * and per-UID basis.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public BatteryUsageStats getBatteryUsageStats() {
+ return getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT);
+ }
+
+ /**
+ * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+ * and per-UID basis.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+ return getBatteryUsageStats(List.of(query)).get(0);
+ }
+
+ /**
+ * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+ * and per-UID basis.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+ try {
+ return mBatteryStats.getBatteryUsageStats(queries);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that the wifi connection RSSI has changed.
+ *
+ * @param newRssi The new RSSI value.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiRssiChanged(@IntRange(from = -127, to = 0) int newRssi) {
+ try {
+ mBatteryStats.noteWifiRssiChanged(newRssi);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that wifi was toggled on.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiOn() {
+ try {
+ mBatteryStats.noteWifiOn();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that wifi was toggled off.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiOff() {
+ try {
+ mBatteryStats.noteWifiOff();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that wifi state has changed.
+ *
+ * @param newWifiState The new wifi State.
+ * @param accessPoint SSID of the network if wifi is connected to STA, else null.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiState(@WifiState int newWifiState,
+ @Nullable String accessPoint) {
+ try {
+ mBatteryStats.noteWifiState(newWifiState, accessPoint);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that a new wifi scan has started.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiScanStartedFromSource(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteWifiScanStartedFromSource(ws);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that an ongoing wifi scan has stopped.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiScanStoppedFromSource(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteWifiScanStoppedFromSource(ws);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that a new wifi batched scan has started.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ * @param csph Channels scanned per hour.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiBatchedScanStartedFromSource(@NonNull WorkSource ws,
+ @IntRange(from = 0) int csph) {
+ try {
+ mBatteryStats.noteWifiBatchedScanStartedFromSource(ws, csph);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that an ongoing wifi batched scan has stopped.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiBatchedScanStoppedFromSource(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteWifiBatchedScanStoppedFromSource(ws);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieves all the cellular related battery stats.
+ *
+ * @return Instance of {@link CellularBatteryStats}.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.BATTERY_STATS,
+ android.Manifest.permission.UPDATE_DEVICE_STATS})
+ public @NonNull CellularBatteryStats getCellularBatteryStats() {
+ try {
+ return mBatteryStats.getCellularBatteryStats();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves all the wifi related battery stats.
+ *
+ * @return Instance of {@link WifiBatteryStats}.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.BATTERY_STATS,
+ android.Manifest.permission.UPDATE_DEVICE_STATS})
+ public @NonNull WifiBatteryStats getWifiBatteryStats() {
+ try {
+ return mBatteryStats.getWifiBatteryStats();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves accumulate wake lock stats.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public WakeLockStats getWakeLockStats() {
+ try {
+ return mBatteryStats.getWakeLockStats();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieves accumulated bluetooth stats.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public BluetoothBatteryStats getBluetoothBatteryStats() {
+ try {
+ return mBatteryStats.getBluetoothBatteryStats();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates an app acquiring full wifi lock.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportFullWifiLockAcquiredFromSource(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteFullWifiLockAcquiredFromSource(ws);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates an app releasing full wifi lock.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportFullWifiLockReleasedFromSource(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteFullWifiLockReleasedFromSource(ws);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that supplicant state has changed.
+ *
+ * @param newSupplState The new Supplicant state.
+ * @param failedAuth Boolean indicating whether there was a connection failure due to
+ * authentication failure.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiSupplicantStateChanged(@WifiSupplState int newSupplState,
+ boolean failedAuth) {
+ try {
+ mBatteryStats.noteWifiSupplicantStateChanged(newSupplState, failedAuth);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that an app has acquired the wifi multicast lock.
+ *
+ * @param ws Worksource with the uid of the app that acquired the wifi lock (to be used for
+ * battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiMulticastEnabled(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteWifiMulticastEnabled(ws.getAttributionUid());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that an app has released the wifi multicast lock.
+ *
+ * @param ws Worksource with the uid of the app that released the wifi lock (to be used for
+ * battery blaming).
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiMulticastDisabled(@NonNull WorkSource ws) {
+ try {
+ mBatteryStats.noteWifiMulticastDisabled(ws.getAttributionUid());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that the radio power state has changed.
+ *
+ * @param isActive indicates if the mobile radio is powered.
+ * @param uid Uid of this event. For the active state it represents the uid that was responsible
+ * for waking the radio, or -1 if the system was responsible for waking the radio.
+ * For inactive state, the UID should always be -1.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportMobileRadioPowerState(boolean isActive, int uid) {
+ try {
+ mBatteryStats.noteMobileRadioPowerState(getDataConnectionPowerState(isActive),
+ SystemClock.elapsedRealtimeNanos(), uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that the wifi power state has changed.
+ *
+ * @param isActive indicates if the wifi radio is powered.
+ * @param uid Uid of this event. For the active state it represents the uid that was responsible
+ * for waking the radio, or -1 if the system was responsible for waking the radio.
+ * For inactive state, the UID should always be -1.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportWifiRadioPowerState(boolean isActive, int uid) {
+ try {
+ mBatteryStats.noteWifiRadioPowerState(getDataConnectionPowerState(isActive),
+ SystemClock.elapsedRealtimeNanos(), uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies the battery stats of a new interface, and the transport types of the network that
+ * includes that interface.
+ *
+ * @param iface The interface of the network.
+ * @param transportTypes The transport type of the network {@link Transport}.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public void reportNetworkInterfaceForTransports(@NonNull String iface,
+ @NonNull int[] transportTypes) throws RuntimeException {
+ try {
+ mBatteryStats.noteNetworkInterfaceForTransports(iface, transportTypes);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that Bluetooth was toggled on.
+ *
+ * @param uid calling package uid
+ * @param reason why Bluetooth has been turned on
+ * @param packageName package responsible for this change
+ * @Deprecated Bluetooth self report its state and no longer call this
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
+ }
+
+ /**
+ * Indicates that Bluetooth was toggled off.
+ *
+ * @param uid calling package uid
+ * @param reason why Bluetooth has been turned on
+ * @param packageName package responsible for this change
+ * @Deprecated Bluetooth self report its state and no longer call this
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
+ }
+
+ /**
+ * Indicates that a new Bluetooth LE scan has started.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ * @param isUnoptimized whether or not the scan has a filter.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportBleScanStarted(@NonNull WorkSource ws, boolean isUnoptimized) {
+ try {
+ mBatteryStats.noteBleScanStarted(ws, isUnoptimized);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that an ongoing Bluetooth LE scan has stopped.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ * @param isUnoptimized whether or not the scan has a filter.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportBleScanStopped(@NonNull WorkSource ws, boolean isUnoptimized) {
+ try {
+ mBatteryStats.noteBleScanStopped(ws, isUnoptimized);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that Bluetooth LE has been reset.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportBleScanReset() {
+ try {
+ mBatteryStats.noteBleScanReset();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that Bluetooth LE scan has received new results.
+ *
+ * @param ws worksource (to be used for battery blaming).
+ * @param numNewResults number of results received since last update.
+ */
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void reportBleScanResults(@NonNull WorkSource ws, int numNewResults) {
+ try {
+ mBatteryStats.noteBleScanResults(ws, numNewResults);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private static int getDataConnectionPowerState(boolean isActive) {
+ // TODO: DataConnectionRealTimeInfo is under telephony package but the constants are used
+ // for both Wifi and mobile. It would make more sense to separate the constants to a
+ // generic class or move it to generic package.
+ return isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ }
+
+ /**
+ * Sets battery AC charger to enabled/disabled, and freezes the battery state.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void setChargerAcOnline(boolean online, boolean forceUpdate) {
+ try {
+ mBatteryStats.setChargerAcOnline(online, forceUpdate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets battery level, and freezes the battery state.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void setBatteryLevel(int level, boolean forceUpdate) {
+ try {
+ mBatteryStats.setBatteryLevel(level, forceUpdate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unplugs battery, and freezes the battery state.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void unplugBattery(boolean forceUpdate) {
+ try {
+ mBatteryStats.unplugBattery(forceUpdate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unfreezes battery state, returning to current hardware values.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void resetBattery(boolean forceUpdate) {
+ try {
+ mBatteryStats.resetBattery(forceUpdate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Suspend charging even if plugged in.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void suspendBatteryInput() {
+ try {
+ mBatteryStats.suspendBatteryInput();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/android-34/android/os/BatteryUsageStats.java b/android-34/android/os/BatteryUsageStats.java
new file mode 100644
index 0000000..e2c52ce
--- /dev/null
+++ b/android-34/android/os/BatteryUsageStats.java
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.database.Cursor;
+import android.database.CursorWindow;
+import android.util.Range;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis.
+ * <p>
+ * The totals for the entire device are returned as AggregateBatteryConsumers, which can be
+ * obtained by calling {@link #getAggregateBatteryConsumer(int)}.
+ * <p>
+ * Power attributed to individual apps is returned as UidBatteryConsumers, see
+ * {@link #getUidBatteryConsumers()}.
+ *
+ * @hide
+ */
+public final class BatteryUsageStats implements Parcelable, Closeable {
+
+ /**
+ * Scope of battery stats included in a BatteryConsumer: the entire device, just
+ * the apps, etc.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"AGGREGATE_BATTERY_CONSUMER_SCOPE_"}, value = {
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public static @interface AggregateBatteryConsumerScope {
+ }
+
+ /**
+ * Power consumption by the entire device, since last charge. The power usage in this
+ * scope includes both the power attributed to apps and the power unattributed to any
+ * apps.
+ */
+ public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE = 0;
+
+ /**
+ * Aggregated power consumed by all applications, combined, since last charge. This is
+ * the sum of power reported in UidBatteryConsumers.
+ */
+ public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS = 1;
+
+ public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2;
+
+ // XML tags and attributes for BatteryUsageStats persistence
+ static final String XML_TAG_BATTERY_USAGE_STATS = "battery_usage_stats";
+ static final String XML_TAG_AGGREGATE = "aggregate";
+ static final String XML_TAG_UID = "uid";
+ static final String XML_TAG_USER = "user";
+ static final String XML_TAG_POWER_COMPONENTS = "power_components";
+ static final String XML_TAG_COMPONENT = "component";
+ static final String XML_TAG_CUSTOM_COMPONENT = "custom_component";
+ static final String XML_ATTR_ID = "id";
+ static final String XML_ATTR_UID = "uid";
+ static final String XML_ATTR_USER_ID = "user_id";
+ static final String XML_ATTR_SCOPE = "scope";
+ static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_";
+ static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data";
+ static final String XML_ATTR_START_TIMESTAMP = "start_timestamp";
+ static final String XML_ATTR_END_TIMESTAMP = "end_timestamp";
+ static final String XML_ATTR_PROCESS_STATE = "process_state";
+ static final String XML_ATTR_POWER = "power";
+ static final String XML_ATTR_DURATION = "duration";
+ static final String XML_ATTR_MODEL = "model";
+ static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity";
+ static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct";
+ static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower";
+ static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper";
+ static final String XML_ATTR_DISCHARGE_DURATION = "discharge_duration";
+ static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining";
+ static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining";
+ static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package";
+ static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground";
+ static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background";
+
+ // We need about 700 bytes per UID
+ private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700;
+
+ private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
+
+ private final int mDischargePercentage;
+ private final double mBatteryCapacityMah;
+ private final long mStatsStartTimestampMs;
+ private final long mStatsEndTimestampMs;
+ private final long mStatsDurationMs;
+ private final double mDischargedPowerLowerBound;
+ private final double mDischargedPowerUpperBound;
+ private final long mDischargeDurationMs;
+ private final long mBatteryTimeRemainingMs;
+ private final long mChargeTimeRemainingMs;
+ private final String[] mCustomPowerComponentNames;
+ private final boolean mIncludesPowerModels;
+ private final boolean mIncludesProcessStateData;
+ private final List<UidBatteryConsumer> mUidBatteryConsumers;
+ private final List<UserBatteryConsumer> mUserBatteryConsumers;
+ private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
+ private final BatteryStatsHistory mBatteryStatsHistory;
+ private CursorWindow mBatteryConsumersCursorWindow;
+
+ private BatteryUsageStats(@NonNull Builder builder) {
+ mStatsStartTimestampMs = builder.mStatsStartTimestampMs;
+ mStatsEndTimestampMs = builder.mStatsEndTimestampMs;
+ mStatsDurationMs = builder.getStatsDuration();
+ mBatteryCapacityMah = builder.mBatteryCapacityMah;
+ mDischargePercentage = builder.mDischargePercentage;
+ mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
+ mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
+ mDischargeDurationMs = builder.mDischargeDurationMs;
+ mBatteryStatsHistory = builder.mBatteryStatsHistory;
+ mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
+ mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
+ mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
+ mIncludesPowerModels = builder.mIncludePowerModels;
+ mIncludesProcessStateData = builder.mIncludesProcessStateData;
+ mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow;
+
+ double totalPowerMah = 0;
+ final int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
+ mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
+ for (int i = 0; i < uidBatteryConsumerCount; i++) {
+ final UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
+ builder.mUidBatteryConsumerBuilders.valueAt(i);
+ if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
+ final UidBatteryConsumer consumer = uidBatteryConsumerBuilder.build();
+ totalPowerMah += consumer.getConsumedPower();
+ mUidBatteryConsumers.add(consumer);
+ }
+ }
+
+ final int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
+ mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
+ for (int i = 0; i < userBatteryConsumerCount; i++) {
+ final UserBatteryConsumer consumer =
+ builder.mUserBatteryConsumerBuilders.valueAt(i).build();
+ totalPowerMah += consumer.getConsumedPower();
+ mUserBatteryConsumers.add(consumer);
+ }
+
+ builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(totalPowerMah);
+
+ mAggregateBatteryConsumers =
+ new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+ for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+ mAggregateBatteryConsumers[i] = builder.mAggregateBatteryConsumersBuilders[i].build();
+ }
+ }
+
+ /**
+ * Timestamp (as returned by System.currentTimeMillis()) of the latest battery stats reset, in
+ * milliseconds.
+ */
+ public long getStatsStartTimestamp() {
+ return mStatsStartTimestampMs;
+ }
+
+ /**
+ * Timestamp (as returned by System.currentTimeMillis()) of when the stats snapshot was taken,
+ * in milliseconds.
+ */
+ public long getStatsEndTimestamp() {
+ return mStatsEndTimestampMs;
+ }
+
+ /**
+ * Returns the duration of the stats session captured by this BatteryUsageStats.
+ * In rare cases, statsDuration != statsEndTimestamp - statsStartTimestamp. This may
+ * happen when BatteryUsageStats represents an accumulation of data across multiple
+ * non-contiguous sessions.
+ */
+ public long getStatsDuration() {
+ return mStatsDurationMs;
+ }
+
+ /**
+ * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully
+ * charged), in mAh
+ */
+ public double getConsumedPower() {
+ return mAggregateBatteryConsumers[AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE]
+ .getConsumedPower();
+ }
+
+ /**
+ * Returns battery capacity in milli-amp-hours.
+ */
+ public double getBatteryCapacity() {
+ return mBatteryCapacityMah;
+ }
+
+ /**
+ * Portion of battery charge drained since BatteryStats reset (e.g. due to being fully
+ * charged), as percentage of the full charge in the range [0:100]. May exceed 100 if
+ * the device repeatedly charged and discharged prior to the reset.
+ */
+ public int getDischargePercentage() {
+ return mDischargePercentage;
+ }
+
+ /**
+ * Returns the discharged power since BatteryStats were last reset, in mAh as an estimated
+ * range.
+ */
+ public Range<Double> getDischargedPowerRange() {
+ return Range.create(mDischargedPowerLowerBound, mDischargedPowerUpperBound);
+ }
+
+ /**
+ * Returns the total amount of time the battery was discharging.
+ */
+ public long getDischargeDurationMs() {
+ return mDischargeDurationMs;
+ }
+
+ /**
+ * Returns an approximation for how much run time (in milliseconds) 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.
+ */
+ public long getBatteryTimeRemainingMs() {
+ return mBatteryTimeRemainingMs;
+ }
+
+ /**
+ * Returns 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.
+ */
+ public long getChargeTimeRemainingMs() {
+ return mChargeTimeRemainingMs;
+ }
+
+ /**
+ * Returns a battery consumer for the specified battery consumer type.
+ */
+ public AggregateBatteryConsumer getAggregateBatteryConsumer(
+ @AggregateBatteryConsumerScope int scope) {
+ return mAggregateBatteryConsumers[scope];
+ }
+
+ @NonNull
+ public List<UidBatteryConsumer> getUidBatteryConsumers() {
+ return mUidBatteryConsumers;
+ }
+
+ @NonNull
+ public List<UserBatteryConsumer> getUserBatteryConsumers() {
+ return mUserBatteryConsumers;
+ }
+
+ /**
+ * Returns the names of custom power components in order, so the first name in the array
+ * corresponds to the custom componentId
+ * {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}.
+ */
+ @NonNull
+ public String[] getCustomPowerComponentNames() {
+ return mCustomPowerComponentNames;
+ }
+
+ public boolean isProcessStateDataIncluded() {
+ return mIncludesProcessStateData;
+ }
+
+ /**
+ * Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
+ */
+ @NonNull
+ public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
+ if (mBatteryStatsHistory == null) {
+ throw new IllegalStateException(
+ "Battery history was not requested in the BatteryUsageStatsQuery");
+ }
+ return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private BatteryUsageStats(@NonNull Parcel source) {
+ mStatsStartTimestampMs = source.readLong();
+ mStatsEndTimestampMs = source.readLong();
+ mStatsDurationMs = source.readLong();
+ mBatteryCapacityMah = source.readDouble();
+ mDischargePercentage = source.readInt();
+ mDischargedPowerLowerBound = source.readDouble();
+ mDischargedPowerUpperBound = source.readDouble();
+ mDischargeDurationMs = source.readLong();
+ mBatteryTimeRemainingMs = source.readLong();
+ mChargeTimeRemainingMs = source.readLong();
+ mCustomPowerComponentNames = source.readStringArray();
+ mIncludesPowerModels = source.readBoolean();
+ mIncludesProcessStateData = source.readBoolean();
+
+ mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
+ BatteryConsumer.BatteryConsumerDataLayout dataLayout =
+ BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames,
+ mIncludesPowerModels, mIncludesProcessStateData);
+
+ final int numRows = mBatteryConsumersCursorWindow.getNumRows();
+
+ mAggregateBatteryConsumers =
+ new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+ mUidBatteryConsumers = new ArrayList<>(numRows);
+ mUserBatteryConsumers = new ArrayList<>();
+
+ for (int i = 0; i < numRows; i++) {
+ final BatteryConsumer.BatteryConsumerData data =
+ new BatteryConsumer.BatteryConsumerData(mBatteryConsumersCursorWindow, i,
+ dataLayout);
+
+ int consumerType = mBatteryConsumersCursorWindow.getInt(i,
+ BatteryConsumer.COLUMN_INDEX_BATTERY_CONSUMER_TYPE);
+ switch (consumerType) {
+ case AggregateBatteryConsumer.CONSUMER_TYPE_AGGREGATE: {
+ final AggregateBatteryConsumer consumer = new AggregateBatteryConsumer(data);
+ mAggregateBatteryConsumers[consumer.getScope()] = consumer;
+ break;
+ }
+ case UidBatteryConsumer.CONSUMER_TYPE_UID: {
+ mUidBatteryConsumers.add(new UidBatteryConsumer(data));
+ break;
+ }
+ case UserBatteryConsumer.CONSUMER_TYPE_USER:
+ mUserBatteryConsumers.add(new UserBatteryConsumer(data));
+ break;
+ }
+ }
+
+ if (source.readBoolean()) {
+ mBatteryStatsHistory = BatteryStatsHistory.createFromBatteryUsageStatsParcel(source);
+ } else {
+ mBatteryStatsHistory = null;
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mStatsStartTimestampMs);
+ dest.writeLong(mStatsEndTimestampMs);
+ dest.writeLong(mStatsDurationMs);
+ dest.writeDouble(mBatteryCapacityMah);
+ dest.writeInt(mDischargePercentage);
+ dest.writeDouble(mDischargedPowerLowerBound);
+ dest.writeDouble(mDischargedPowerUpperBound);
+ dest.writeLong(mDischargeDurationMs);
+ dest.writeLong(mBatteryTimeRemainingMs);
+ dest.writeLong(mChargeTimeRemainingMs);
+ dest.writeStringArray(mCustomPowerComponentNames);
+ dest.writeBoolean(mIncludesPowerModels);
+ dest.writeBoolean(mIncludesProcessStateData);
+
+ mBatteryConsumersCursorWindow.writeToParcel(dest, flags);
+
+ if (mBatteryStatsHistory != null) {
+ dest.writeBoolean(true);
+ mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest);
+ } else {
+ dest.writeBoolean(false);
+ }
+ }
+
+ @NonNull
+ public static final Creator<BatteryUsageStats> CREATOR = new Creator<BatteryUsageStats>() {
+ public BatteryUsageStats createFromParcel(@NonNull Parcel source) {
+ return new BatteryUsageStats(source);
+ }
+
+ public BatteryUsageStats[] newArray(int size) {
+ return new BatteryUsageStats[size];
+ }
+ };
+
+ /** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */
+ public byte[] getStatsProto() {
+ // ProtoOutputStream.getRawSize() returns the buffer size before compaction.
+ // BatteryUsageStats contains a lot of integers, so compaction of integers to
+ // varint reduces the size of the proto buffer by as much as 50%.
+ int maxRawSize = (int) (STATSD_PULL_ATOM_MAX_BYTES * 1.75);
+ // Limit the number of attempts in order to prevent an infinite loop
+ for (int i = 0; i < 3; i++) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+ writeStatsProto(proto, maxRawSize);
+
+ final int rawSize = proto.getRawSize();
+ final byte[] protoOutput = proto.getBytes();
+
+ if (protoOutput.length <= STATSD_PULL_ATOM_MAX_BYTES) {
+ return protoOutput;
+ }
+
+ // Adjust maxRawSize proportionately and try again.
+ maxRawSize =
+ (int) ((long) STATSD_PULL_ATOM_MAX_BYTES * rawSize / protoOutput.length - 1024);
+ }
+
+ // Fallback: if we have failed to generate a proto smaller than STATSD_PULL_ATOM_MAX_BYTES,
+ // just generate a proto with the _rawSize_ of STATSD_PULL_ATOM_MAX_BYTES, which is
+ // guaranteed to produce a compacted proto (significantly) smaller than
+ // STATSD_PULL_ATOM_MAX_BYTES.
+ final ProtoOutputStream proto = new ProtoOutputStream();
+ writeStatsProto(proto, STATSD_PULL_ATOM_MAX_BYTES);
+ return proto.getBytes();
+ }
+
+ /**
+ * Writes contents in a binary protobuffer format, using
+ * the android.os.BatteryUsageStatsAtomsProto proto.
+ */
+ public void dumpToProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ writeStatsProto(proto, /* max size */ Integer.MAX_VALUE);
+ proto.flush();
+ }
+
+ @NonNull
+ private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) {
+ final AggregateBatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+ proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp());
+ proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, getStatsEndTimestamp());
+ proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
+ proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
+ getDischargePercentage());
+ proto.write(BatteryUsageStatsAtomsProto.DISCHARGE_DURATION_MILLIS,
+ getDischargeDurationMs());
+ deviceBatteryConsumer.writeStatsProto(proto,
+ BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
+ if (mIncludesPowerModels) {
+ deviceBatteryConsumer.writePowerComponentModelProto(proto);
+ }
+ writeUidBatteryConsumersProto(proto, maxRawSize);
+ }
+
+ /**
+ * Writes the UidBatteryConsumers data, held by this BatteryUsageStats, to the proto (as used
+ * for atoms.proto).
+ */
+ private void writeUidBatteryConsumersProto(ProtoOutputStream proto, int maxRawSize) {
+ final List<UidBatteryConsumer> consumers = getUidBatteryConsumers();
+ // Order consumers by descending weight (a combination of consumed power and usage time)
+ consumers.sort(Comparator.comparingDouble(this::getUidBatteryConsumerWeight).reversed());
+
+ final int size = consumers.size();
+ for (int i = 0; i < size; i++) {
+ final UidBatteryConsumer consumer = consumers.get(i);
+
+ final long fgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
+ final long bgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
+ final boolean hasBaseData = consumer.hasStatsProtoData();
+
+ if (fgMs == 0 && bgMs == 0 && !hasBaseData) {
+ continue;
+ }
+
+ final long token = proto.start(BatteryUsageStatsAtomsProto.UID_BATTERY_CONSUMERS);
+ proto.write(
+ BatteryUsageStatsAtomsProto.UidBatteryConsumer.UID,
+ consumer.getUid());
+ if (hasBaseData) {
+ consumer.writeStatsProto(proto,
+ BatteryUsageStatsAtomsProto.UidBatteryConsumer.BATTERY_CONSUMER_DATA);
+ }
+ proto.write(
+ BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_FOREGROUND_MILLIS,
+ fgMs);
+ proto.write(
+ BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS,
+ bgMs);
+ proto.end(token);
+
+ if (proto.getRawSize() >= maxRawSize) {
+ break;
+ }
+ }
+ }
+
+ private static final double WEIGHT_CONSUMED_POWER = 1;
+ // Weight one hour in foreground the same as 100 mAh of power drain
+ private static final double WEIGHT_FOREGROUND_STATE = 100.0 / (1 * 60 * 60 * 1000);
+ // Weight one hour in background the same as 300 mAh of power drain
+ private static final double WEIGHT_BACKGROUND_STATE = 300.0 / (1 * 60 * 60 * 1000);
+
+ /**
+ * Computes the weight associated with a UidBatteryConsumer, which is used for sorting.
+ * We want applications with the largest consumed power as well as applications
+ * with the highest usage time to be included in the statsd atom.
+ */
+ private double getUidBatteryConsumerWeight(UidBatteryConsumer uidBatteryConsumer) {
+ final double consumedPower = uidBatteryConsumer.getConsumedPower();
+ final long timeInForeground =
+ uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
+ final long timeInBackground =
+ uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
+ return consumedPower * WEIGHT_CONSUMED_POWER
+ + timeInForeground * WEIGHT_FOREGROUND_STATE
+ + timeInBackground * WEIGHT_BACKGROUND_STATE;
+ }
+
+ /**
+ * Prints the stats in a human-readable format.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println(" Estimated power use (mAh):");
+ pw.print(prefix);
+ pw.print(" Capacity: ");
+ pw.print(BatteryStats.formatCharge(getBatteryCapacity()));
+ pw.print(", Computed drain: ");
+ pw.print(BatteryStats.formatCharge(getConsumedPower()));
+ final Range<Double> dischargedPowerRange = getDischargedPowerRange();
+ pw.print(", actual drain: ");
+ pw.print(BatteryStats.formatCharge(dischargedPowerRange.getLower()));
+ if (!dischargedPowerRange.getLower().equals(dischargedPowerRange.getUpper())) {
+ pw.print("-");
+ pw.print(BatteryStats.formatCharge(dischargedPowerRange.getUpper()));
+ }
+ pw.println();
+
+ pw.println(" Global");
+ final BatteryConsumer deviceConsumer = getAggregateBatteryConsumer(
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ final BatteryConsumer appsConsumer = getAggregateBatteryConsumer(
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+
+ for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+ for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) {
+ final double devicePowerMah = deviceConsumer.getConsumedPower(key);
+ final double appsPowerMah = appsConsumer.getConsumedPower(key);
+ if (devicePowerMah == 0 && appsPowerMah == 0) {
+ continue;
+ }
+
+ String label = BatteryConsumer.powerComponentIdToString(componentId);
+ if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ label = label
+ + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
+ }
+ printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah,
+ deviceConsumer.getPowerModel(key),
+ deviceConsumer.getUsageDurationMillis(key));
+ }
+ }
+
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + mCustomPowerComponentNames.length;
+ componentId++) {
+ final double devicePowerMah =
+ deviceConsumer.getConsumedPowerForCustomComponent(componentId);
+ final double appsPowerMah =
+ appsConsumer.getConsumedPowerForCustomComponent(componentId);
+ if (devicePowerMah == 0 && appsPowerMah == 0) {
+ continue;
+ }
+
+ printPowerComponent(pw, prefix, deviceConsumer.getCustomPowerComponentName(componentId),
+ devicePowerMah, appsPowerMah,
+ BatteryConsumer.POWER_MODEL_UNDEFINED,
+ deviceConsumer.getUsageDurationForCustomComponentMillis(componentId));
+ }
+
+ dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers());
+ dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
+ pw.println();
+ }
+
+ private void printPowerComponent(PrintWriter pw, String prefix, String label,
+ double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(prefix).append(" ").append(label).append(": ")
+ .append(BatteryStats.formatCharge(devicePowerMah));
+ if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
+ && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
+ sb.append(" [");
+ sb.append(BatteryConsumer.powerModelToString(powerModel));
+ sb.append("]");
+ }
+ sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah));
+ if (durationMs != 0) {
+ sb.append(" duration: ");
+ BatteryStats.formatTimeMs(sb, durationMs);
+ }
+
+ pw.println(sb.toString());
+ }
+
+ private void dumpSortedBatteryConsumers(PrintWriter pw, String prefix,
+ List<? extends BatteryConsumer> batteryConsumers) {
+ batteryConsumers.sort(
+ Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
+ .reversed());
+ for (BatteryConsumer consumer : batteryConsumers) {
+ if (consumer.getConsumedPower() == 0) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.print(" ");
+ consumer.dump(pw);
+ pw.println();
+ }
+ }
+
+ /** Serializes this object to XML */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_BATTERY_USAGE_STATS);
+
+ for (int i = 0; i < mCustomPowerComponentNames.length; i++) {
+ serializer.attribute(null, XML_ATTR_PREFIX_CUSTOM_COMPONENT + i,
+ mCustomPowerComponentNames[i]);
+ }
+ serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA,
+ mIncludesProcessStateData);
+ serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs);
+ serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs);
+ serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs);
+ serializer.attributeDouble(null, XML_ATTR_BATTERY_CAPACITY, mBatteryCapacityMah);
+ serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage);
+ serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound);
+ serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound);
+ serializer.attributeLong(null, XML_ATTR_DISCHARGE_DURATION, mDischargeDurationMs);
+ serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs);
+ serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs);
+
+ for (int scope = 0; scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+ scope++) {
+ mAggregateBatteryConsumers[scope].writeToXml(serializer, scope);
+ }
+ for (UidBatteryConsumer consumer : mUidBatteryConsumers) {
+ consumer.writeToXml(serializer);
+ }
+ for (UserBatteryConsumer consumer : mUserBatteryConsumers) {
+ consumer.writeToXml(serializer);
+ }
+ serializer.endTag(null, XML_TAG_BATTERY_USAGE_STATS);
+ }
+
+ /** Parses an XML representation of BatteryUsageStats */
+ public static BatteryUsageStats createFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ Builder builder = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_BATTERY_USAGE_STATS)) {
+ List<String> customComponentNames = new ArrayList<>();
+ int i = 0;
+ while (true) {
+ int index = parser.getAttributeIndex(null,
+ XML_ATTR_PREFIX_CUSTOM_COMPONENT + i);
+ if (index == -1) {
+ break;
+ }
+ customComponentNames.add(parser.getAttributeValue(index));
+ i++;
+ }
+
+ final boolean includesProcStateData = parser.getAttributeBoolean(null,
+ XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
+
+ builder = new Builder(customComponentNames.toArray(new String[0]), true,
+ includesProcStateData);
+
+ builder.setStatsStartTimestamp(
+ parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
+ builder.setStatsEndTimestamp(
+ parser.getAttributeLong(null, XML_ATTR_END_TIMESTAMP));
+ builder.setStatsDuration(
+ parser.getAttributeLong(null, XML_ATTR_DURATION));
+ builder.setBatteryCapacity(
+ parser.getAttributeDouble(null, XML_ATTR_BATTERY_CAPACITY));
+ builder.setDischargePercentage(
+ parser.getAttributeInt(null, XML_ATTR_DISCHARGE_PERCENT));
+ builder.setDischargedPowerRange(
+ parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER),
+ parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER));
+ builder.setDischargeDurationMs(
+ parser.getAttributeLong(null, XML_ATTR_DISCHARGE_DURATION));
+ builder.setBatteryTimeRemainingMs(
+ parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING));
+ builder.setChargeTimeRemainingMs(
+ parser.getAttributeLong(null, XML_ATTR_CHARGE_REMAINING));
+
+ eventType = parser.next();
+ break;
+ }
+ eventType = parser.next();
+ }
+
+ if (builder == null) {
+ throw new XmlPullParserException("No root element");
+ }
+
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_AGGREGATE:
+ AggregateBatteryConsumer.parseXml(parser, builder);
+ break;
+ case XML_TAG_UID:
+ UidBatteryConsumer.createFromXml(parser, builder);
+ break;
+ case XML_TAG_USER:
+ UserBatteryConsumer.createFromXml(parser, builder);
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+
+ return builder.build();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mBatteryConsumersCursorWindow.close();
+ mBatteryConsumersCursorWindow = null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mBatteryConsumersCursorWindow != null) {
+ mBatteryConsumersCursorWindow.close();
+ }
+ super.finalize();
+ }
+
+ /**
+ * Builder for BatteryUsageStats.
+ */
+ public static final class Builder {
+ private final CursorWindow mBatteryConsumersCursorWindow;
+ @NonNull
+ private final String[] mCustomPowerComponentNames;
+ private final boolean mIncludePowerModels;
+ private final boolean mIncludesProcessStateData;
+ private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
+ private long mStatsStartTimestampMs;
+ private long mStatsEndTimestampMs;
+ private long mStatsDurationMs = -1;
+ private double mBatteryCapacityMah;
+ private int mDischargePercentage;
+ private double mDischargedPowerLowerBoundMah;
+ private double mDischargedPowerUpperBoundMah;
+ private long mDischargeDurationMs;
+ private long mBatteryTimeRemainingMs = -1;
+ private long mChargeTimeRemainingMs = -1;
+ private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders =
+ new AggregateBatteryConsumer.Builder[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+ private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders =
+ new SparseArray<>();
+ private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
+ new SparseArray<>();
+ private BatteryStatsHistory mBatteryStatsHistory;
+
+ public Builder(@NonNull String[] customPowerComponentNames) {
+ this(customPowerComponentNames, false, false);
+ }
+
+ public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
+ boolean includeProcessStateData) {
+ mBatteryConsumersCursorWindow =
+ new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
+ mBatteryConsumerDataLayout =
+ BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames,
+ includePowerModels, includeProcessStateData);
+ mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
+
+ mCustomPowerComponentNames = customPowerComponentNames;
+ mIncludePowerModels = includePowerModels;
+ mIncludesProcessStateData = includeProcessStateData;
+ for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
+ final BatteryConsumer.BatteryConsumerData data =
+ BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
+ mBatteryConsumerDataLayout);
+ mAggregateBatteryConsumersBuilders[scope] =
+ new AggregateBatteryConsumer.Builder(data, scope);
+ }
+ }
+
+ public boolean isProcessStateDataNeeded() {
+ return mIncludesProcessStateData;
+ }
+
+ /**
+ * Constructs a read-only object using the Builder values.
+ */
+ @NonNull
+ public BatteryUsageStats build() {
+ return new BatteryUsageStats(this);
+ }
+
+ /**
+ * Sets the battery capacity in milli-amp-hours.
+ */
+ public Builder setBatteryCapacity(double batteryCapacityMah) {
+ mBatteryCapacityMah = batteryCapacityMah;
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of the latest battery stats reset, in milliseconds.
+ */
+ public Builder setStatsStartTimestamp(long statsStartTimestampMs) {
+ mStatsStartTimestampMs = statsStartTimestampMs;
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of when the battery stats snapshot was taken, in milliseconds.
+ */
+ public Builder setStatsEndTimestamp(long statsEndTimestampMs) {
+ mStatsEndTimestampMs = statsEndTimestampMs;
+ return this;
+ }
+
+ /**
+ * Sets the duration of the stats session. The default value of this field is
+ * statsEndTimestamp - statsStartTimestamp.
+ */
+ public Builder setStatsDuration(long statsDurationMs) {
+ mStatsDurationMs = statsDurationMs;
+ return this;
+ }
+
+ private long getStatsDuration() {
+ if (mStatsDurationMs != -1) {
+ return mStatsDurationMs;
+ } else {
+ return mStatsEndTimestampMs - mStatsStartTimestampMs;
+ }
+ }
+
+ /**
+ * Sets the battery discharge amount since BatteryStats reset as percentage of the full
+ * charge.
+ */
+ @NonNull
+ public Builder setDischargePercentage(int dischargePercentage) {
+ mDischargePercentage = dischargePercentage;
+ return this;
+ }
+
+ /**
+ * Sets the estimated battery discharge range.
+ */
+ @NonNull
+ public Builder setDischargedPowerRange(double dischargedPowerLowerBoundMah,
+ double dischargedPowerUpperBoundMah) {
+ mDischargedPowerLowerBoundMah = dischargedPowerLowerBoundMah;
+ mDischargedPowerUpperBoundMah = dischargedPowerUpperBoundMah;
+ return this;
+ }
+
+ /**
+ * Sets the total battery discharge time, in milliseconds.
+ */
+ @NonNull
+ public Builder setDischargeDurationMs(long durationMs) {
+ mDischargeDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Sets an approximation for how much time (in milliseconds) remains until the battery
+ * is fully discharged.
+ */
+ @NonNull
+ public Builder setBatteryTimeRemainingMs(long batteryTimeRemainingMs) {
+ mBatteryTimeRemainingMs = batteryTimeRemainingMs;
+ return this;
+ }
+
+ /**
+ * Sets an approximation for how much time (in milliseconds) remains until the battery
+ * is fully charged.
+ */
+ @NonNull
+ public Builder setChargeTimeRemainingMs(long chargeTimeRemainingMs) {
+ mChargeTimeRemainingMs = chargeTimeRemainingMs;
+ return this;
+ }
+
+ /**
+ * Sets the parceled recent history.
+ */
+ @NonNull
+ public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory) {
+ mBatteryStatsHistory = batteryStatsHistory;
+ return this;
+ }
+
+ /**
+ * Creates or returns an AggregateBatteryConsumer builder, which represents aggregate
+ * battery consumption data for the specified scope.
+ */
+ @NonNull
+ public AggregateBatteryConsumer.Builder getAggregateBatteryConsumerBuilder(
+ @AggregateBatteryConsumerScope int scope) {
+ return mAggregateBatteryConsumersBuilders[scope];
+ }
+
+ /**
+ * Creates or returns a UidBatteryConsumer, which represents battery attribution
+ * data for an individual UID.
+ */
+ @NonNull
+ public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(
+ @NonNull BatteryStats.Uid batteryStatsUid) {
+ int uid = batteryStatsUid.getUid();
+ UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid);
+ if (builder == null) {
+ final BatteryConsumer.BatteryConsumerData data =
+ BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
+ mBatteryConsumerDataLayout);
+ builder = new UidBatteryConsumer.Builder(data, batteryStatsUid);
+ mUidBatteryConsumerBuilders.put(uid, builder);
+ }
+ return builder;
+ }
+
+ /**
+ * Creates or returns a UidBatteryConsumer, which represents battery attribution
+ * data for an individual UID. This version of the method is not suitable for use
+ * with PowerCalculators.
+ */
+ @NonNull
+ public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(int uid) {
+ UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid);
+ if (builder == null) {
+ final BatteryConsumer.BatteryConsumerData data =
+ BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
+ mBatteryConsumerDataLayout);
+ builder = new UidBatteryConsumer.Builder(data, uid);
+ mUidBatteryConsumerBuilders.put(uid, builder);
+ }
+ return builder;
+ }
+
+ /**
+ * Creates or returns a UserBatteryConsumer, which represents battery attribution
+ * data for an individual {@link UserHandle}.
+ */
+ @NonNull
+ public UserBatteryConsumer.Builder getOrCreateUserBatteryConsumerBuilder(int userId) {
+ UserBatteryConsumer.Builder builder = mUserBatteryConsumerBuilders.get(userId);
+ if (builder == null) {
+ final BatteryConsumer.BatteryConsumerData data =
+ BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
+ mBatteryConsumerDataLayout);
+ builder = new UserBatteryConsumer.Builder(data, userId);
+ mUserBatteryConsumerBuilders.put(userId, builder);
+ }
+ return builder;
+ }
+
+ @NonNull
+ public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() {
+ return mUidBatteryConsumerBuilders;
+ }
+
+ /**
+ * Adds battery usage stats from another snapshots. The two snapshots are assumed to be
+ * non-overlapping, meaning that the power consumption estimates and session durations
+ * can be simply summed across the two snapshots. This remains true even if the timestamps
+ * seem to indicate that the sessions are in fact overlapping: timestamps may be off as a
+ * result of realtime clock adjustments by the user or the system.
+ */
+ @NonNull
+ public Builder add(BatteryUsageStats stats) {
+ if (!Arrays.equals(mCustomPowerComponentNames, stats.mCustomPowerComponentNames)) {
+ throw new IllegalArgumentException(
+ "BatteryUsageStats have different custom power components");
+ }
+
+ if (mIncludesProcessStateData && !stats.mIncludesProcessStateData) {
+ throw new IllegalArgumentException(
+ "Added BatteryUsageStats does not include process state data");
+ }
+
+ if (mUserBatteryConsumerBuilders.size() != 0
+ || !stats.getUserBatteryConsumers().isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Combining UserBatteryConsumers is not supported");
+ }
+
+ mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound;
+ mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound;
+ mDischargePercentage += stats.mDischargePercentage;
+ mDischargeDurationMs += stats.mDischargeDurationMs;
+
+ mStatsDurationMs = getStatsDuration() + stats.getStatsDuration();
+
+ if (mStatsStartTimestampMs == 0
+ || stats.mStatsStartTimestampMs < mStatsStartTimestampMs) {
+ mStatsStartTimestampMs = stats.mStatsStartTimestampMs;
+ }
+
+ final boolean addingLaterSnapshot = stats.mStatsEndTimestampMs > mStatsEndTimestampMs;
+ if (addingLaterSnapshot) {
+ mStatsEndTimestampMs = stats.mStatsEndTimestampMs;
+ }
+
+ for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
+ getAggregateBatteryConsumerBuilder(scope)
+ .add(stats.mAggregateBatteryConsumers[scope]);
+ }
+
+ for (UidBatteryConsumer consumer : stats.getUidBatteryConsumers()) {
+ getOrCreateUidBatteryConsumerBuilder(consumer.getUid()).add(consumer);
+ }
+
+ if (addingLaterSnapshot) {
+ mBatteryCapacityMah = stats.mBatteryCapacityMah;
+ mBatteryTimeRemainingMs = stats.mBatteryTimeRemainingMs;
+ mChargeTimeRemainingMs = stats.mChargeTimeRemainingMs;
+ }
+
+ return this;
+ }
+
+ /**
+ * Dumps raw contents of the cursor window for debugging.
+ */
+ void dump(PrintWriter writer) {
+ final int numRows = mBatteryConsumersCursorWindow.getNumRows();
+ int numColumns = mBatteryConsumerDataLayout.columnCount;
+ for (int i = 0; i < numRows; i++) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < numColumns; j++) {
+ final int type = mBatteryConsumersCursorWindow.getType(i, j);
+ switch (type) {
+ case Cursor.FIELD_TYPE_NULL:
+ sb.append("null, ");
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ sb.append(mBatteryConsumersCursorWindow.getInt(i, j)).append(", ");
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ sb.append(mBatteryConsumersCursorWindow.getFloat(i, j)).append(", ");
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ sb.append(mBatteryConsumersCursorWindow.getString(i, j)).append(", ");
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ sb.append("BLOB, ");
+ break;
+ }
+ }
+ sb.setLength(sb.length() - 2);
+ writer.println(sb);
+ }
+ }
+ }
+}
diff --git a/android-34/android/os/BatteryUsageStatsQuery.java b/android-34/android/os/BatteryUsageStatsQuery.java
new file mode 100644
index 0000000..b3f4d98
--- /dev/null
+++ b/android-34/android/os/BatteryUsageStatsQuery.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.IntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Query parameters for the {@link BatteryStatsManager#getBatteryUsageStats()} call.
+ *
+ * @hide
+ */
+public final class BatteryUsageStatsQuery implements Parcelable {
+
+ @NonNull
+ public static final BatteryUsageStatsQuery DEFAULT =
+ new BatteryUsageStatsQuery.Builder().build();
+
+ /**
+ * Flags for the {@link BatteryStatsManager#getBatteryUsageStats()} method.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FLAG_BATTERY_USAGE_STATS_" }, value = {
+ FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL,
+ FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY,
+ FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA,
+ FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BatteryUsageStatsFlags {}
+
+ /**
+ * Indicates that power estimations should be based on the usage time and
+ * average power constants provided in the PowerProfile, even if on-device power monitoring
+ * is available.
+ *
+ * @hide
+ */
+ public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 0x0001;
+
+ /**
+ * Indicates that battery history should be included in the BatteryUsageStats.
+ * @hide
+ */
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 0x0002;
+
+ /**
+ * Indicates that identifiers of power models used for computations of power
+ * consumption should be included in the BatteryUsageStats.
+ */
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS = 0x0004;
+
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA = 0x0008;
+
+ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010;
+
+ private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
+
+ private final int mFlags;
+ @NonNull
+ private final int[] mUserIds;
+ private final long mMaxStatsAgeMs;
+ private final long mFromTimestamp;
+ private final long mToTimestamp;
+ private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
+
+ private BatteryUsageStatsQuery(@NonNull Builder builder) {
+ mFlags = builder.mFlags;
+ mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
+ : new int[]{UserHandle.USER_ALL};
+ mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
+ mFromTimestamp = builder.mFromTimestamp;
+ mToTimestamp = builder.mToTimestamp;
+ mPowerComponents = builder.mPowerComponents;
+ }
+
+ @BatteryUsageStatsFlags
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Returns an array of users for which the attribution is requested. It may
+ * contain {@link UserHandle#USER_ALL} to indicate that the attribution
+ * should be performed for all users. Battery consumed by users <b>not</b> included
+ * in this array will be returned in the aggregated form as {@link UserBatteryConsumer}'s.
+ */
+ @NonNull
+ public int[] getUserIds() {
+ return mUserIds;
+ }
+
+ /**
+ * Returns true if the power calculations must be based on the PowerProfile constants,
+ * even if measured energy data is available.
+ */
+ public boolean shouldForceUsePowerProfileModel() {
+ return (mFlags & FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;
+ }
+
+ public boolean isProcessStateDataNeeded() {
+ return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
+ }
+
+ /**
+ * Returns the power components that should be estimated or null if all power components
+ * are being requested.
+ */
+ public int[] getPowerComponents() {
+ return mPowerComponents;
+ }
+
+ /**
+ * Returns the client's tolerance for stale battery stats. The data is allowed to be up to
+ * this many milliseconds out-of-date.
+ */
+ public long getMaxStatsAge() {
+ return mMaxStatsAgeMs;
+ }
+
+ /**
+ * Returns the exclusive lower bound of the stored snapshot timestamps that should be included
+ * in the aggregation. Ignored if {@link #getToTimestamp()} is zero.
+ */
+ public long getFromTimestamp() {
+ return mFromTimestamp;
+ }
+
+ /**
+ * Returns the inclusive upper bound of the stored snapshot timestamps that should
+ * be included in the aggregation. The default is to include only the current stats
+ * accumulated since the latest battery reset.
+ */
+ public long getToTimestamp() {
+ return mToTimestamp;
+ }
+
+ private BatteryUsageStatsQuery(Parcel in) {
+ mFlags = in.readInt();
+ mUserIds = new int[in.readInt()];
+ in.readIntArray(mUserIds);
+ mMaxStatsAgeMs = in.readLong();
+ mFromTimestamp = in.readLong();
+ mToTimestamp = in.readLong();
+ mPowerComponents = in.createIntArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFlags);
+ dest.writeInt(mUserIds.length);
+ dest.writeIntArray(mUserIds);
+ dest.writeLong(mMaxStatsAgeMs);
+ dest.writeLong(mFromTimestamp);
+ dest.writeLong(mToTimestamp);
+ dest.writeIntArray(mPowerComponents);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<BatteryUsageStatsQuery> CREATOR =
+ new Creator<BatteryUsageStatsQuery>() {
+ @Override
+ public BatteryUsageStatsQuery createFromParcel(Parcel in) {
+ return new BatteryUsageStatsQuery(in);
+ }
+
+ @Override
+ public BatteryUsageStatsQuery[] newArray(int size) {
+ return new BatteryUsageStatsQuery[size];
+ }
+ };
+
+ /**
+ * Builder for BatteryUsageStatsQuery.
+ */
+ public static final class Builder {
+ private int mFlags;
+ private IntArray mUserIds;
+ private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
+ private long mFromTimestamp;
+ private long mToTimestamp;
+ private @BatteryConsumer.PowerComponent int[] mPowerComponents;
+
+ /**
+ * Builds a read-only BatteryUsageStatsQuery object.
+ */
+ public BatteryUsageStatsQuery build() {
+ return new BatteryUsageStatsQuery(this);
+ }
+
+ /**
+ * Add a user whose battery stats should be included in the battery usage stats.
+ * {@link UserHandle#USER_ALL} will be used by default if no users are added explicitly.
+ */
+ public Builder addUser(@NonNull UserHandle userHandle) {
+ if (mUserIds == null) {
+ mUserIds = new IntArray(1);
+ }
+ mUserIds.add(userHandle.getIdentifier());
+ return this;
+ }
+
+ /**
+ * Requests that battery history be included in the BatteryUsageStats.
+ */
+ public Builder includeBatteryHistory() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY;
+ return this;
+ }
+
+ /**
+ * Requests that per-process state data be included in the BatteryUsageStats, if
+ * available. Check {@link BatteryUsageStats#isProcessStateDataIncluded()} on the result
+ * to see if the data is available.
+ */
+ public Builder includeProcessStateData() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA;
+ return this;
+ }
+
+ /**
+ * Requests to return modeled battery usage stats only, even if on-device
+ * power monitoring data is available.
+ *
+ * Should only be used for testing and debugging.
+ */
+ public Builder powerProfileModeledOnly() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
+ return this;
+ }
+
+ /**
+ * Requests to return identifiers of models that were used for estimation
+ * of power consumption.
+ *
+ * Should only be used for testing and debugging.
+ */
+ public Builder includePowerModels() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS;
+ return this;
+ }
+
+ /**
+ * Requests to return only statistics for the specified power components. The default
+ * is all power components.
+ */
+ public Builder includePowerComponents(
+ @BatteryConsumer.PowerComponent int[] powerComponents) {
+ mPowerComponents = powerComponents;
+ return this;
+ }
+
+ /**
+ * Requests to return attribution data for virtual UIDs such as
+ * {@link Process#SDK_SANDBOX_VIRTUAL_UID}.
+ */
+ public Builder includeVirtualUids() {
+ mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS;
+ return this;
+ }
+
+ /**
+ * Requests to aggregate stored snapshots between the two supplied timestamps
+ * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
+ * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
+ */
+ public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
+ mFromTimestamp = fromTimestamp;
+ mToTimestamp = toTimestamp;
+ return this;
+ }
+
+ /**
+ * Set the client's tolerance for stale battery stats. The data may be up to
+ * this many milliseconds out-of-date.
+ */
+ public Builder setMaxStatsAgeMs(long maxStatsAgeMs) {
+ mMaxStatsAgeMs = maxStatsAgeMs;
+ return this;
+ }
+ }
+}
diff --git a/android-34/android/os/BestClock.java b/android-34/android/os/BestClock.java
new file mode 100644
index 0000000..aa066b6
--- /dev/null
+++ b/android-34/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-34/android/os/Binder.java b/android-34/android/os/Binder.java
new file mode 100644
index 0000000..00676f3
--- /dev/null
+++ b/android-34/android/os/Binder.java
@@ -0,0 +1,1418 @@
+/*
+ * 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.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ExceptionUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderCallHeavyHitterWatcher;
+import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
+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.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * 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>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.
+ *
+ * @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.
+ *
+ * <p>This constant needs to be kept in sync with IPCThreadState::kUnsetWorkSource.
+ *
+ * @hide
+ */
+ public static final int UNSET_WORKSOURCE = -1;
+
+ /**
+ * Control whether {@link #dump(FileDescriptor, PrintWriter, String[]) 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);
+ }
+
+ /**
+ * The watcher to monitor the heavy hitter from incoming transactions
+ */
+ private static volatile BinderCallHeavyHitterWatcher sHeavyHitterWatcher = null;
+
+ // Transaction tracking code.
+
+ /**
+ * Flag indicating whether we should be tracing transact calls.
+ */
+ private static volatile boolean sStackTrackingEnabled = false;
+
+ /**
+ * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
+ * {@link TransactionTracker}.
+ *
+ * @hide
+ */
+ public static void enableStackTracking() {
+ sStackTrackingEnabled = true;
+ }
+
+ /**
+ * Disable Binder IPC stack tracking.
+ *
+ * @hide
+ */
+ public static void disableStackTracking() {
+ sStackTrackingEnabled = false;
+ }
+
+ /**
+ * Check if binder transaction stack tracking is enabled.
+ *
+ * @hide
+ */
+ public static boolean isStackTrackingEnabled() {
+ return sStackTrackingEnabled;
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ static ThreadLocal<Boolean> sWarnOnBlockingOnCurrentThread =
+ ThreadLocal.withInitial(() -> sWarnOnBlocking);
+
+ /**
+ * Allow blocking calls for the current thread.
+ *
+ * @see {@link #allowBlocking}.
+ *
+ * @hide
+ */
+ public static void allowBlockingForCurrentThread() {
+ sWarnOnBlockingOnCurrentThread.set(false);
+ }
+
+ /**
+ * Reset the current thread to the default blocking behavior.
+ *
+ * @see {@link #defaultBlocking}.
+ *
+ * @hide
+ */
+ public static void defaultBlockingForCurrentThread() {
+ sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking);
+ }
+
+ /**
+ * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
+ */
+ @UnsupportedAppUsage
+ private final long mObject;
+
+ private IInterface mOwner;
+ @Nullable
+ private String mDescriptor;
+ private volatile AtomicReferenceArray<String> mTransactionTraceNames = null;
+ private volatile String mSimpleDescriptor = null;
+ private static final int TRANSACTION_TRACE_NAME_ID_LIMIT = 1024;
+
+ /**
+ * 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.
+ *
+ * Warning: oneway transactions do not receive PID.
+ */
+ @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 isDirectlyHandlingTransaction();
+
+ /**
+ * Returns {@code true} if the current thread has had its identity
+ * set explicitly via {@link #clearCallingIdentity()}
+ *
+ * @hide
+ */
+ @CriticalNative
+ private static native boolean hasExplicitIdentity();
+
+ /**
+ * 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 and the calling identity has not been
+ * explicitly set with {@link #clearCallingIdentity()}
+ */
+ public static final int getCallingUidOrThrow() {
+ if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
+ throw new IllegalStateException(
+ "Thread is not in a binder transaction, "
+ + "and the calling identity has not been "
+ + "explicitly set with clearCallingIdentity");
+ }
+ return getCallingUid();
+ }
+
+ /**
+ * Return the Linux UID assigned to the process that sent the transaction
+ * currently being processed.
+ *
+ * Slog.wtf if the current thread is not currently
+ * executing an incoming transaction and the calling identity has not been
+ * explicitly set with {@link #clearCallingIdentity()}
+ *
+ * @hide
+ */
+ public static final int getCallingUidOrWtf(String message) {
+ if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
+ Slog.wtf(TAG,
+ message + ": Thread is not in a binder transaction, "
+ + "and the calling identity has not been "
+ + "explicitly set with clearCallingIdentity");
+ }
+ 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.
+ *
+ * @see UserHandle
+ */
+ 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
+ */
+ @CriticalNative
+ public static final native void restoreCallingIdentity(long token);
+
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}.
+ *
+ * <p>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) {
+ Throwable throwableToPropagate = null;
+ final long callingIdentity = clearCallingIdentity();
+ 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.
+ *
+ * <p>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) {
+ Throwable throwableToPropagate = null;
+ final long callingIdentity = clearCallingIdentity();
+ 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 #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.
+ *
+ * <p>Unlike {@link #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 #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);
+
+ /**
+ * Mark as being built with VINTF-level stability promise. This API should
+ * only ever be invoked by generated code from the aidl compiler. It means
+ * that the interface represented by this binder is guaranteed to be kept
+ * stable for several years, according to the VINTF compatibility lifecycle,
+ * and the build system also keeps snapshots of these APIs and invokes the
+ * AIDL compiler to make sure that these snapshots are backwards compatible.
+ * Instead of using this API, use the @VintfStability annotation on your
+ * AIDL interface.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public final native void markVintfStability();
+
+ /**
+ * Use a VINTF-stability binder w/o VINTF requirements. Should be called
+ * on a binder before it is sent out of process.
+ *
+ * <p>This must be called before the object is sent to another process.
+ *
+ * @hide
+ */
+ public final native void forceDowngradeToSystemStability();
+
+ /**
+ * 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.
+ *
+ * <p>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.
+ *
+ * <p>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, {@link #queryLocalInterface(String) 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 {@link #attachInterface attachInterface()}
+ * to return the associated {@link 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.
+ *
+ * @param msg The message to show instead of the dump; if null, dumps are
+ * re-enabled.
+ *
+ * @hide
+ */
+ 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 {@link #onTransactEnded} (or null).,
+ *
+ * @hide
+ */
+ @Nullable
+ default Object onTransactStarted(@NonNull IBinder binder, int transactionCode, int flags) {
+ return onTransactStarted(binder, transactionCode);
+ }
+
+ /**
+ * Called before onTransact.
+ *
+ * @return an object that will be passed back to {@link #onTransactEnded} (or null).
+ */
+ @Nullable
+ Object onTransactStarted(@NonNull IBinder binder, int transactionCode);
+
+ /**
+ * Called after onTransact (even when an exception is thrown).
+ *
+ * @param session The object return by {@link #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 #getCallingUid()} is already set to the UID of the current
+ // process when this method is called.
+ //
+ // We use {@link 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.
+ *
+ * @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;
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public final @NonNull String getTransactionTraceName(int transactionCode) {
+ if (mTransactionTraceNames == null) {
+ final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
+ mSimpleDescriptor = getSimpleDescriptor();
+ mTransactionTraceNames = new AtomicReferenceArray(highestId + 1);
+ }
+
+ final int index = transactionCode - FIRST_CALL_TRANSACTION;
+ if (index < 0 || index >= mTransactionTraceNames.length()) {
+ return mSimpleDescriptor + "#" + transactionCode;
+ }
+
+ String transactionTraceName = mTransactionTraceNames.getAcquire(index);
+ if (transactionTraceName == null) {
+ final String transactionName = getTransactionName(transactionCode);
+ final StringBuffer buf = new StringBuffer();
+
+ // Keep trace name consistent with cpp trace name in:
+ // system/tools/aidl/generate_cpp.cpp
+ buf.append("AIDL::java::");
+ if (transactionName != null) {
+ buf.append(mSimpleDescriptor).append("::").append(transactionName);
+ } else {
+ buf.append(mSimpleDescriptor).append("::#").append(transactionCode);
+ }
+ buf.append("::server");
+
+ transactionTraceName = buf.toString();
+ mTransactionTraceNames.setRelease(index, transactionTraceName);
+ }
+
+ return transactionTraceName;
+ }
+
+ private @NonNull String getSimpleDescriptor() {
+ String descriptor = mDescriptor;
+ if (descriptor == null) {
+ // Just "Binder" to avoid null checks in transaction name tracing.
+ return "Binder";
+ }
+
+ final int dot = descriptor.lastIndexOf(".");
+ if (dot > 0) {
+ // Strip the package name
+ return descriptor.substring(dot + 1);
+ }
+ return descriptor;
+ }
+
+ /**
+ * @return The highest user-defined transaction id of all transactions.
+ * @hide
+ */
+ public int getMaxTransactionId() {
+ return 0;
+ }
+
+ /**
+ * 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}.
+ *
+ * <p>The default implementation performs a caller check to make sure the caller UID is of
+ * SHELL or ROOT, and then call {@link #handleShellCommand}.
+ *
+ * <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.
+ *
+ * @hide
+ */
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+ resultReceiver.send(-1, null);
+ throw new SecurityException("Shell commands are only callable by ADB");
+ }
+
+ // First, convert in, out and err to @NonNull, by redirecting any that's null to /dev/null.
+ try {
+ if (in == null) {
+ in = new FileInputStream("/dev/null").getFD();
+ }
+ if (out == null) {
+ out = new FileOutputStream("/dev/null").getFD();
+ }
+ if (err == null) {
+ err = out;
+ }
+ } catch (IOException e) {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(err != null ? err : out));
+ pw.println("Failed to open /dev/null: " + e.getMessage());
+ pw.flush();
+ resultReceiver.send(-1, null);
+ return;
+ }
+ // Also make args @NonNull.
+ if (args == null) {
+ args = new String[0];
+ }
+
+ int result = -1;
+ try (ParcelFileDescriptor inPfd = ParcelFileDescriptor.dup(in);
+ ParcelFileDescriptor outPfd = ParcelFileDescriptor.dup(out);
+ ParcelFileDescriptor errPfd = ParcelFileDescriptor.dup(err)) {
+ result = handleShellCommand(inPfd, outPfd, errPfd, args);
+ } catch (IOException e) {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(err));
+ pw.println("dup() failed: " + e.getMessage());
+ pw.flush();
+ } finally {
+ resultReceiver.send(result, null);
+ }
+ }
+
+ /**
+ * System services can implement this method to implement ADB shell commands.
+ *
+ * <p>A system binder service can implement it to handle shell commands on ADB. For example,
+ * the Job Scheduler service implements it to handle {@code adb shell cmd jobscheduler}.
+ *
+ * <p>Commands are only executable by ADB shell; i.e. only {@link Process#SHELL_UID} and
+ * {@link Process#ROOT_UID} can call them.
+ *
+ * @param in standard input
+ * @param out standard output
+ * @param err standard error
+ * @param args arguments passed to the command. Can be empty. The first argument is typically
+ * a subcommand, such as {@code run} for {@code adb shell cmd jobscheduler run}.
+ * @return the status code returned from the {@code cmd} command.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor());
+ PrintWriter pw = new FastPrintWriter(ferr);
+ pw.println("No shell command implementation.");
+ pw.flush();
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public final native @Nullable IBinder getExtension();
+
+ /**
+ * Set the binder extension.
+ * This should be called immediately when the object is created.
+ *
+ * @hide
+ */
+ public final native void setExtension(@Nullable IBinder extension);
+
+ /**
+ * 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();
+
+ /**
+ * 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) {
+
+ Parcel data = Parcel.obtain(dataObj);
+ Parcel reply = Parcel.obtain(replyObj);
+
+ // At that point, the parcel request headers haven't been parsed so we do not know what
+ // {@link WorkSource} the caller has set. Use calling UID as the default.
+ //
+ // TODO: this is wrong - we should attribute along the entire call route
+ // also this attribution logic should move to native code - it only works
+ // for Java now
+ //
+ // This attribution support is not generic and therefore not support in RPC mode
+ final int callingUid = data.isForRpc() ? -1 : Binder.getCallingUid();
+ final long origWorkSource = callingUid == -1
+ ? -1 : ThreadLocalWorkSource.setUid(callingUid);
+
+ try {
+ return execTransactInternal(code, data, reply, flags, callingUid);
+ } finally {
+ reply.recycle();
+ data.recycle();
+
+ if (callingUid != -1) {
+ ThreadLocalWorkSource.restore(origWorkSource);
+ }
+ }
+ }
+
+ private boolean execTransactInternal(int code, Parcel data, Parcel reply, 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;
+ // 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 {@link IBinder#FLAG_ONEWAY} then these exceptions
+ // disappear into the ether.
+ final boolean tagEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL);
+ final boolean hasFullyQualifiedName = getMaxTransactionId() > 0;
+ final String transactionTraceName;
+
+ if (tagEnabled && hasFullyQualifiedName) {
+ // If tracing enabled and we have a fully qualified name, fetch the name
+ transactionTraceName = getTransactionTraceName(code);
+ } else if (tagEnabled && isStackTrackingEnabled()) {
+ // If tracing is enabled and we *don't* have a fully qualified name, fetch the
+ // 'best effort' name only for stack tracking. This works around noticeable perf impact
+ // on low latency binder calls (<100us). The tracing call itself is between (1-10us) and
+ // the perf impact can be quite noticeable while benchmarking such binder calls.
+ // The primary culprits are ContentProviders and Cursors which convenienty don't
+ // autogenerate their AIDL and hence will not have a fully qualified name.
+ //
+ // TODO(b/253426478): Relax this constraint after a more robust fix
+ transactionTraceName = getTransactionTraceName(code);
+ } else {
+ transactionTraceName = null;
+ }
+
+ final boolean tracingEnabled = tagEnabled && transactionTraceName != null;
+ try {
+ // TODO - this logic should not be in Java - it should be in native
+ // code in libbinder so that it works for all binder users.
+ final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
+ if (heavyHitterWatcher != null && callingUid != -1) {
+ // Notify the heavy hitter watcher, if it's enabled.
+ heavyHitterWatcher.onTransaction(callingUid, getClass(), code);
+ }
+ if (tracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_AIDL, transactionTraceName);
+ }
+
+ // TODO - this logic should not be in Java - it should be in native
+ // code in libbinder so that it works for all binder users. Further,
+ // this should not re-use flags.
+ if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0 && callingUid != -1) {
+ AppOpsManager.startNotedAppOpsCollection(callingUid);
+ try {
+ res = onTransact(code, data, reply, flags);
+ } finally {
+ AppOpsManager.finishNotedAppOpsCollection();
+ }
+ } else {
+ 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_AIDL);
+ }
+ 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");
+ }
+
+ // 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;
+ }
+
+ /**
+ * Set the configuration for the heavy hitter watcher.
+ *
+ * @hide
+ */
+ public static synchronized void setHeavyHitterWatcherConfig(final boolean enabled,
+ final int batchSize, final float threshold,
+ @Nullable final BinderCallHeavyHitterListener listener) {
+ Slog.i(TAG, "Setting heavy hitter watcher config: "
+ + enabled + ", " + batchSize + ", " + threshold);
+ BinderCallHeavyHitterWatcher watcher = sHeavyHitterWatcher;
+ if (enabled) {
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+ boolean newWatcher = false;
+ if (watcher == null) {
+ watcher = BinderCallHeavyHitterWatcher.getInstance();
+ newWatcher = true;
+ }
+ watcher.setConfig(true, batchSize, threshold, listener);
+ if (newWatcher) {
+ sHeavyHitterWatcher = watcher;
+ }
+ } else if (watcher != null) {
+ watcher.setConfig(false, 0, 0.0f, null);
+ }
+ }
+}
diff --git a/android-34/android/os/BinderCallsStatsPerfTest.java b/android-34/android/os/BinderCallsStatsPerfTest.java
new file mode 100644
index 0000000..12e49e3
--- /dev/null
+++ b/android-34/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-34/android/os/BinderProxy.java b/android-34/android/os/BinderProxy.java
new file mode 100644
index 0000000..1929a4d
--- /dev/null
+++ b/android-34/android/os/BinderProxy.java
@@ -0,0 +1,716 @@
+/*
+ * 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.app.ActivityManager;
+import android.app.AppOpsManager;
+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;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 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;
+
+ private static class BinderProxyMapSizeException extends AssertionError {
+ BinderProxyMapSizeException(String s) {
+ super(s);
+ }
+ };
+
+ /**
+ * @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 BinderProxyMapSizeException if the number of
+ * map entries exceeds:
+ */
+ private static final int CRASH_AT_SIZE = 25_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.refersTo(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).refersTo(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).refersTo(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 BinderProxyMapSizeException(
+ "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<>();
+ final ArrayList<WeakReference<BinderProxy>> proxiesToQuery =
+ new ArrayList<WeakReference<BinderProxy>>();
+ synchronized (sProxyMap) {
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ proxiesToQuery.addAll(a);
+ }
+ }
+ }
+ // For gathering this debug output, we're making synchronous binder calls
+ // out of system_server to all processes hosting binder objects it holds a reference to;
+ // since some of those processes might be frozen, we don't want to block here
+ // forever. Disable the freezer.
+ try {
+ ActivityManager.getService().enableAppFreezer(false);
+ } catch (RemoteException e) {
+ Log.e(Binder.TAG, "RemoteException while disabling app freezer");
+ }
+
+ // We run the dump on a separate thread, because there are known cases where
+ // a process overrides getInterfaceDescriptor() and somehow blocks on it, causing
+ // the calling thread (usually AMS) to hit the watchdog.
+ // Do the dumping on a separate thread instead, and give up after a while.
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+ executorService.submit(() -> {
+ for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
+ 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);
+ }
+ }
+ });
+
+ try {
+ executorService.shutdown();
+ boolean dumpDone = executorService.awaitTermination(20, TimeUnit.SECONDS);
+ if (!dumpDone) {
+ Log.e(Binder.TAG, "Failed to complete binder proxy dump,"
+ + " dumping what we have so far.");
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ try {
+ ActivityManager.getService().enableAppFreezer(true);
+ } catch (RemoteException e) {
+ Log.e(Binder.TAG, "RemoteException while re-enabling app freezer");
+ }
+ 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) {
+ 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) {
+ 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;
+ }
+
+ /** @hide */
+ @Override
+ public native @Nullable IBinder getExtension() throws RemoteException;
+
+ /**
+ * 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");
+
+ boolean warnOnBlocking = mWarnOnBlocking; // Cache it to reduce volatile access.
+
+ if (warnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
+ && Binder.sWarnOnBlockingOnCurrentThread.get()) {
+
+ // For now, avoid spamming the log by disabling after we've logged
+ // about this interface at least once
+ mWarnOnBlocking = false;
+ warnOnBlocking = false;
+
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ // Log this as a WTF on userdebug and eng builds.
+ Log.wtf(Binder.TAG,
+ "Outgoing transactions from this process must be FLAG_ONEWAY",
+ new Throwable());
+ } else {
+ Log.w(Binder.TAG,
+ "Outgoing transactions from this process must be FLAG_ONEWAY",
+ new Throwable());
+ }
+ }
+
+ final boolean tracingEnabled = Binder.isStackTrackingEnabled();
+ 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, flags);
+
+ // 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);
+ }
+ }
+
+ final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
+ AppOpsManager.pauseNotedAppOpsCollection();
+
+ if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isListeningForOpNoted()) {
+ flags |= FLAG_COLLECT_NOTED_APP_OPS;
+ }
+
+ try {
+ final boolean result = transactNative(code, data, reply, flags);
+
+ if (reply != null && !warnOnBlocking) {
+ reply.addFlags(Parcel.FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT);
+ }
+
+ return result;
+ } finally {
+ AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
+
+ 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, IBinder binderProxy) {
+ if (false) {
+ Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy);
+ }
+ try {
+ recipient.binderDied(binderProxy);
+ } 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-34/android/os/BluetoothBatteryStats.java b/android-34/android/os/BluetoothBatteryStats.java
new file mode 100644
index 0000000..3d99a08
--- /dev/null
+++ b/android-34/android/os/BluetoothBatteryStats.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Snapshot of Bluetooth battery stats.
+ *
+ * @hide
+ */
+public class BluetoothBatteryStats implements Parcelable {
+
+ /** @hide */
+ public static class UidStats {
+ public final int uid;
+ public final long scanTimeMs;
+ public final long unoptimizedScanTimeMs;
+ public final int scanResultCount;
+ public final long rxTimeMs;
+ public final long txTimeMs;
+
+ public UidStats(int uid, long scanTimeMs, long unoptimizedScanTimeMs, int scanResultCount,
+ long rxTimeMs, long txTimeMs) {
+ this.uid = uid;
+ this.scanTimeMs = scanTimeMs;
+ this.unoptimizedScanTimeMs = unoptimizedScanTimeMs;
+ this.scanResultCount = scanResultCount;
+ this.rxTimeMs = rxTimeMs;
+ this.txTimeMs = txTimeMs;
+ }
+
+ private UidStats(Parcel in) {
+ uid = in.readInt();
+ scanTimeMs = in.readLong();
+ unoptimizedScanTimeMs = in.readLong();
+ scanResultCount = in.readInt();
+ rxTimeMs = in.readLong();
+ txTimeMs = in.readLong();
+ }
+
+ private void writeToParcel(Parcel out) {
+ out.writeInt(uid);
+ out.writeLong(scanTimeMs);
+ out.writeLong(unoptimizedScanTimeMs);
+ out.writeInt(scanResultCount);
+ out.writeLong(rxTimeMs);
+ out.writeLong(txTimeMs);
+ }
+
+ @Override
+ public String toString() {
+ return "UidStats{"
+ + "uid=" + uid
+ + ", scanTimeMs=" + scanTimeMs
+ + ", unoptimizedScanTimeMs=" + unoptimizedScanTimeMs
+ + ", scanResultCount=" + scanResultCount
+ + ", rxTimeMs=" + rxTimeMs
+ + ", txTimeMs=" + txTimeMs
+ + '}';
+ }
+ }
+
+ private final List<UidStats> mUidStats;
+
+ public BluetoothBatteryStats(@NonNull List<UidStats> uidStats) {
+ mUidStats = uidStats;
+ }
+
+ @NonNull
+ public List<UidStats> getUidStats() {
+ return mUidStats;
+ }
+
+ protected BluetoothBatteryStats(Parcel in) {
+ final int size = in.readInt();
+ mUidStats = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mUidStats.add(new UidStats(in));
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ final int size = mUidStats.size();
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ UidStats stats = mUidStats.get(i);
+ stats.writeToParcel(out);
+ }
+ }
+
+ public static final Creator<BluetoothBatteryStats> CREATOR =
+ new Creator<BluetoothBatteryStats>() {
+ @Override
+ public BluetoothBatteryStats createFromParcel(Parcel in) {
+ return new BluetoothBatteryStats(in);
+ }
+
+ @Override
+ public BluetoothBatteryStats[] newArray(int size) {
+ return new BluetoothBatteryStats[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android-34/android/os/BluetoothServiceManager.java b/android-34/android/os/BluetoothServiceManager.java
new file mode 100644
index 0000000..12f7bc8
--- /dev/null
+++ b/android-34/android/os/BluetoothServiceManager.java
@@ -0,0 +1,121 @@
+/*
+ * 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.Nullable;
+import android.content.Context;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.os.BluetoothServiceManager;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the bluetooth
+ * service.
+ *
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class BluetoothServiceManager {
+
+ /** @hide */
+ public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
+ /**
+ * @hide
+ */
+ public BluetoothServiceManager() {
+ }
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Register a system server binding object for a service.
+ */
+ public void register(@NonNull IBinder service) {
+ ServiceManager.addService(mServiceName, service);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @NonNull
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ */
+ @Nullable
+ public IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow}.
+ *
+ */
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor.
+ *
+ * @param name the name of the binder service that cannot be found.
+ *
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "bluetooth" service.
+ */
+ @NonNull
+ public ServiceRegisterer getBluetoothManagerServiceRegisterer() {
+ return new ServiceRegisterer(BLUETOOTH_MANAGER_SERVICE);
+ }
+}
diff --git a/android-34/android/os/Broadcaster.java b/android-34/android/os/Broadcaster.java
new file mode 100644
index 0000000..88760b0
--- /dev/null
+++ b/android-34/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.compat.annotation.UnsupportedAppUsage;
+
+/** @hide */
+public class Broadcaster
+{
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/BugreportManager.java b/android-34/android/os/BugreportManager.java
new file mode 100644
index 0000000..086b0e5
--- /dev/null
+++ b/android-34/android/os/BugreportManager.java
@@ -0,0 +1,489 @@
+/*
+ * 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.Manifest;
+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.SuppressAutoDoc;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.WorkerThread;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.internal.R;
+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.
+ *
+ * <p>This class may only be used by apps that currently have carrier privileges (see {@link
+ * android.telephony.TelephonyManager#hasCarrierPrivileges}) on an active SIM or priv-apps
+ * explicitly allowed by the device manufacturer.
+ *
+ * <p>Only one bugreport can be generated by the system at a time.
+ */
+@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.
+ *
+ * <p>Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a
+ * terminal call to either {@link #onFinished} or {@link #onError}.
+ *
+ * <p>If an issue is encountered while starting the bugreport asynchronously, callers will
+ * receive an {@link #onError} call without any {@link #onProgress} callbacks.
+ */
+ public abstract static class BugreportCallback {
+ /**
+ * Possible error codes taking a bugreport can encounter.
+ *
+ * @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,
+ BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE
+ })
+ public @interface BugreportErrorCode {}
+
+ /**
+ * The input options were invalid. For example, the destination file the app provided could
+ * not be written by the system.
+ */
+ public static final int BUGREPORT_ERROR_INVALID_INPUT =
+ IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
+
+ /** A runtime error occurred. */
+ 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;
+
+ /** There is no bugreport to retrieve for the caller. */
+ public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE =
+ IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE;
+
+ /**
+ * 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.
+ *
+ * <p>This callback will be invoked if the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
+ */
+ public void onFinished() {}
+
+ /** Called when taking bugreport finishes successfully.
+ *
+ * <p>This callback will only be invoked if the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is set. Otherwise, the
+ * {@link #onFinished()} callback will be invoked.
+ *
+ * @param bugreportFile the absolute path of the generated bugreport file.
+ * @hide
+
+ */
+ @SystemApi
+ public void onFinished(@NonNull String bugreportFile) {}
+
+ /**
+ * Called when it is ready for calling app to show UI, showing any extra UI before this
+ * callback can interfere with bugreport generation.
+ */
+ public void onEarlyReportFinished() {}
+ }
+
+ /**
+ * Speculatively pre-dumps UI data for a bugreport request that might come later.
+ *
+ * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short
+ * ring buffers that might get lost by the time the actual bugreport is requested.
+ *
+ * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following
+ * conditions are met:
+ * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified.
+ * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ @WorkerThread
+ public void preDumpUiData() {
+ try {
+ mBinder.preDumpUiData(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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. If
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} is set, user consent will be deferred
+ * and no files will be copied to the given file descriptors.
+ *
+ * <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
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ @WorkerThread
+ 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);
+
+ boolean deferConsent =
+ (params.getFlags() & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+ boolean isScreenshotRequested = screenshotFd != null || deferConsent;
+ 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, isScreenshotRequested, deferConsent);
+ // Note: mBinder can get callingUid from the binder transaction.
+ mBinder.startBugreport(
+ -1 /* callingUid */,
+ mContext.getOpPackageName(),
+ bugreportFd.getFileDescriptor(),
+ screenshotFd.getFileDescriptor(),
+ params.getMode(),
+ params.getFlags(),
+ dsListener,
+ isScreenshotRequested);
+ } 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);
+ }
+ }
+ }
+
+ /**
+ * Retrieves a previously generated bugreport.
+ *
+ * <p>The previously generated bugreport must have been generated by calling {@link
+ * #startBugreport(ParcelFileDescriptor, ParcelFileDescriptor, BugreportParams,
+ * Executor, BugreportCallback)} with the {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT}
+ * flag set. The bugreport file returned by the {@link BugreportCallback#onFinished(String)}
+ * callback for a previously generated bugreport must be passed to this method. A caller may
+ * only retrieve bugreports that they have previously requested.
+ *
+ * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user
+ * consents to sharing with the calling app.
+ *
+ * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
+ *
+ * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
+ * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
+ *
+ * @param bugreportFile the identifier for a bugreport that was previously generated for this
+ * caller using {@code startBugreport}.
+ * @param bugreportFd file to copy over the previous bugreport. This should be opened in
+ * write-only, append mode.
+ * @param executor the executor to execute callback methods.
+ * @param callback callback for progress and status updates.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DUMP)
+ @WorkerThread
+ public void retrieveBugreport(
+ @NonNull String bugreportFile,
+ @NonNull ParcelFileDescriptor bugreportFd,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BugreportCallback callback
+ ) {
+ try {
+ Preconditions.checkNotNull(bugreportFile);
+ Preconditions.checkNotNull(bugreportFd);
+ Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(callback);
+ DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
+ mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
+ bugreportFd.getFileDescriptor(),
+ bugreportFile,
+ dsListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ IoUtils.closeQuietly(bugreportFd);
+ }
+ }
+
+ /**
+ * Starts a connectivity bugreport.
+ *
+ * <p>The connectivity bugreport is a specialized version of bugreport that only includes
+ * information specifically for debugging connectivity-related issues (e.g. telephony, wi-fi,
+ * and IP networking issues). It is intended primarily for use by OEMs and network providers
+ * such as mobile network operators. In addition to generally excluding information that isn't
+ * targeted to connectivity debugging, this type of bugreport excludes PII and sensitive
+ * information that isn't strictly necessary for connectivity debugging.
+ *
+ * <p>The calling app MUST have a context-specific reason for requesting a connectivity
+ * bugreport, such as detecting a connectivity-related issue. This API SHALL NOT be used to
+ * perform random sampling from a fleet of public end-user devices.
+ *
+ * <p>Calling this API will cause the system to ask the user for consent every single time. The
+ * bugreport artifacts will be copied over to the given file descriptors only if the user
+ * consents to sharing with the calling app.
+ *
+ * <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>Requires that the calling app has carrier privileges (see {@link
+ * android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription.
+ *
+ * @param bugreportFd file to write the bugreport. This should be opened in write-only, append
+ * mode.
+ * @param callback callback for progress and status updates.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @WorkerThread
+ public void startConnectivityBugreport(
+ @NonNull ParcelFileDescriptor bugreportFd,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BugreportCallback callback) {
+ startBugreport(
+ bugreportFd,
+ null /* screenshotFd */,
+ new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY),
+ executor,
+ callback);
+ }
+
+ /**
+ * Cancels the currently running bugreport.
+ *
+ * <p>Apps are only able to cancel their own bugreports. App A cannot cancel a bugreport started
+ * by app B.
+ *
+ * <p>Requires permission: {@link android.Manifest.permission#DUMP} or that the calling app has
+ * carrier privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on
+ * any active subscription.
+ *
+ * @throws SecurityException if trying to cancel another app's bugreport in progress
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @WorkerThread
+ public void cancelBugreport() {
+ try {
+ mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests a bugreport.
+ *
+ * <p>This requests the platform/system to take a bugreport and makes the final bugreport
+ * available to the user. The user may choose to share it with another app, but the bugreport is
+ * never given back directly to the app that requested it.
+ *
+ * @param params {@link BugreportParams} that specify what kind of a bugreport should be taken,
+ * please note that not all kinds of bugreport allow for a progress notification
+ * @param shareTitle title on the final share notification
+ * @param shareDescription description on the final share notification
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DUMP)
+ public void requestBugreport(
+ @NonNull BugreportParams params,
+ @Nullable CharSequence shareTitle,
+ @Nullable CharSequence shareDescription) {
+ try {
+ String title = shareTitle == null ? null : shareTitle.toString();
+ String description = shareDescription == null ? null : shareDescription.toString();
+ ActivityManager.getService()
+ .requestBugReportWithDescription(title, description, params.getMode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private final class DumpstateListener extends IDumpstateListener.Stub {
+ private final Executor mExecutor;
+ private final BugreportCallback mCallback;
+ private final boolean mIsScreenshotRequested;
+ private final boolean mIsConsentDeferred;
+
+ DumpstateListener(
+ Executor executor, BugreportCallback callback, boolean isScreenshotRequested,
+ boolean isConsentDeferred) {
+ mExecutor = executor;
+ mCallback = callback;
+ mIsScreenshotRequested = isScreenshotRequested;
+ mIsConsentDeferred = isConsentDeferred;
+ }
+
+ @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(String bugreportFile) throws RemoteException {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mIsConsentDeferred) {
+ mExecutor.execute(() -> mCallback.onFinished(bugreportFile));
+ } else {
+ mExecutor.execute(() -> mCallback.onFinished());
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onScreenshotTaken(boolean success) throws RemoteException {
+ if (!mIsScreenshotRequested) {
+ return;
+ }
+
+ Handler mainThreadHandler = new Handler(Looper.getMainLooper());
+ mainThreadHandler.post(
+ () -> {
+ int message =
+ success
+ ? R.string.bugreport_screenshot_success_toast
+ : R.string.bugreport_screenshot_failure_toast;
+ Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
+ });
+ }
+
+ @Override
+ public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onEarlyReportFinished());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+}
diff --git a/android-34/android/os/BugreportParams.java b/android-34/android/os/BugreportParams.java
new file mode 100644
index 0000000..d9d14b0
--- /dev/null
+++ b/android-34/android/os/BugreportParams.java
@@ -0,0 +1,150 @@
+/*
+ * 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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Parameters that specify what kind of bugreport should be taken.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BugreportParams {
+ private final int mMode;
+ private final int mFlags;
+
+ /**
+ * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+ *
+ * @param mode of the bugreport to request
+ */
+ public BugreportParams(@BugreportMode int mode) {
+ mMode = mode;
+ mFlags = 0;
+ }
+
+ /**
+ * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+ *
+ * @param mode of the bugreport to request
+ * @param flags to customize the bugreport request
+ */
+ public BugreportParams(@BugreportMode int mode, @BugreportFlag int flags) {
+ mMode = mode;
+ mFlags = flags;
+ }
+
+ /**
+ * Returns the mode of the bugreport to request.
+ */
+ @BugreportMode
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Returns the flags to customize the bugreport request.
+ */
+ @BugreportFlag
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * Defines acceptable flags for customizing bugreport requests.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
+ BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
+ BUGREPORT_FLAG_DEFER_CONSENT
+ })
+ public @interface BugreportFlag {}
+
+ /**
+ * Flag for reusing pre-dumped UI data. The pre-dump and bugreport request calls must be
+ * performed by the same UID, otherwise the flag is ignored.
+ */
+ public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA =
+ IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA;
+
+ /**
+ * Flag for deferring user consent.
+ *
+ * <p>This flag should be used in cases where it may not be possible for the user to respond
+ * to a consent dialog immediately, such as when the user is driving. The generated bugreport
+ * may be retrieved at a later time using {@link BugreportManager#retrieveBugreport(
+ * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+ */
+ public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
+}
diff --git a/android-34/android/os/Build.java b/android-34/android/os/Build.java
new file mode 100644
index 0000000..9f9c222
--- /dev/null
+++ b/android-34/android/os/Build.java
@@ -0,0 +1,1584 @@
+/*
+ * 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.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressAutoDoc;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.sysprop.DeviceProperties;
+import android.sysprop.SocProperties;
+import android.sysprop.TelephonyProperties;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.View;
+
+import dalvik.system.VMRuntime;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 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 product name for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product name which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String PRODUCT_FOR_ATTESTATION = getVendorDeviceIdProperty("name");
+
+ /** The name of the industrial design. */
+ public static final String DEVICE = getString("ro.product.device");
+
+ /**
+ * The device name for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'DEVICE' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the device name which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String DEVICE_FOR_ATTESTATION =
+ getVendorDeviceIdProperty("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 manufacturer name for attestation. In non-default builds (like the AOSP build) the value
+ * of the 'MANUFACTURER' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the manufacturer which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String MANUFACTURER_FOR_ATTESTATION =
+ getVendorDeviceIdProperty("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 product brand for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'BRAND' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product brand which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String BRAND_FOR_ATTESTATION = getVendorDeviceIdProperty("brand");
+
+ /** The end-user-visible name for the end product. */
+ public static final String MODEL = getString("ro.product.model");
+
+ /**
+ * The product model for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'MODEL' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product model which was provisioned.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String MODEL_FOR_ATTESTATION = getVendorDeviceIdProperty("model");
+
+ /** The manufacturer of the device's primary system-on-chip. */
+ @NonNull
+ public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN);
+
+ /** The model name of the device's primary system-on-chip. */
+ @NonNull
+ public static final String SOC_MODEL = SocProperties.soc_model().orElse(UNKNOWN);
+
+ /** 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 = joinListOrElse(
+ TelephonyProperties.baseband_version(), UNKNOWN);
+
+ /** The name of the hardware (from the kernel command line or /proc). */
+ public static final String HARDWARE = getString("ro.hardware");
+
+ /**
+ * The SKU of the hardware (from the kernel command line).
+ *
+ * <p>The SKU is reported by the bootloader to configure system software features.
+ * If no value is supplied by the bootloader, this is reported as {@link #UNKNOWN}.
+
+ */
+ @NonNull
+ public static final String SKU = getString("ro.boot.hardware.sku");
+
+ /**
+ * The SKU of the device as set by the original design manufacturer (ODM).
+ *
+ * <p>This is a runtime-initialized property set during startup to configure device
+ * services. If no value is set, this is reported as {@link #UNKNOWN}.
+ *
+ * <p>The ODM SKU may have multiple variants for the same system SKU in case a manufacturer
+ * produces variants of the same design. For example, the same build may be released with
+ * variations in physical keyboard and/or display hardware, each with a different ODM SKU.
+ */
+ @NonNull
+ public static final String ODM_SKU = getString("ro.boot.product.hardware.sku");
+
+ /**
+ * Whether this build was for an emulator device.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public static final boolean IS_EMULATOR = getString("ro.boot.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 not use
+ * <a href="/training/articles/security-key-attestation.html">key attestation</a> to obtain
+ * proof of the device's original identifiers. KeyMint will reject an ID attestation request
+ * if the identifiers provided by the frameworks do not match the identifiers it was
+ * provisioned with.
+ *
+ * <p>Starting with API level 29, persistent device identifiers are guarded behind additional
+ * restrictions, and apps are recommended to use resettable identifiers (see <a
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
+ * <ul>
+ * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
+ * is a privileged permission that can only be granted to apps preloaded on the device.
+ * <li>If the calling app has carrier privileges (see {@link
+ * android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription.
+ * <li>If the calling app is the default SMS role holder (see {@link
+ * android.app.role.RoleManager#isRoleHeld(String)}).
+ * <li>If the calling app is the device owner of a fully-managed device, a profile
+ * owner of an organization-owned device, or their delegates (see {@link
+ * android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
+ * </ul>
+ *
+ * <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, null);
+ } 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 version string. May be {@link #RELEASE} or {@link #CODENAME} if
+ * not a final release build.
+ */
+ @NonNull public static final String RELEASE_OR_CODENAME = getString(
+ "ro.build.version.release_or_codename");
+
+ /**
+ * The version string we show to the user; may be {@link #RELEASE} or
+ * a descriptive string if not a final release build.
+ */
+ @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY = getString(
+ "ro.build.version.release_or_preview_display");
+
+ /**
+ * 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. This value represents the date when the device
+ * most recently applied a security patch.
+ */
+ public static final String SECURITY_PATCH = SystemProperties.get(
+ "ro.build.version.security_patch", "");
+
+ /**
+ * The media performance class of the device or 0 if none.
+ * <p>
+ * If this value is not <code>0</code>, the device conforms to the media performance class
+ * definition of the SDK version of this value. This value never changes while a device is
+ * booted, but it may increase when the hardware manufacturer provides an OTA update.
+ * <p>
+ * Possible non-zero values are defined in {@link Build.VERSION_CODES} starting with
+ * {@link Build.VERSION_CODES#R}.
+ */
+ public static final int MEDIA_PERFORMANCE_CLASS =
+ DeviceProperties.media_performance_class().orElse(0);
+
+ /**
+ * 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
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static final int DEVICE_INITIAL_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");
+
+ /**
+ * All known codenames that are present in {@link VERSION_CODES}.
+ *
+ * <p>This includes in development codenames as well, i.e. if {@link #CODENAME} is not "REL"
+ * then the value of that is present in this set.
+ *
+ * <p>If a particular string is not present in this set, then it is either not a codename
+ * or a codename for a future release. For example, during Android R development, "Tiramisu"
+ * was not a known codename.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull public static final Set<String> KNOWN_CODENAMES =
+ new ArraySet<>(getStringList("ro.build.version.known_codenames", ","));
+
+ private static final String[] ALL_CODENAMES
+ = getStringList("ro.build.version.all_codenames", ",");
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ 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.
+ */
+ // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+ public static final int CUR_DEVELOPMENT = 10000;
+
+ /**
+ * The original, first, version of Android. Yay!
+ *
+ * <p>Released publicly as Android 1.0 in September 2008.
+ */
+ public static final int BASE = 1;
+
+ /**
+ * First Android update.
+ *
+ * <p>Released publicly as Android 1.1 in February 2009.
+ */
+ public static final int BASE_1_1 = 2;
+
+ /**
+ * C.
+ *
+ * <p>Released publicly as Android 1.5 in April 2009.
+ */
+ public static final int CUPCAKE = 3;
+
+ /**
+ * D.
+ *
+ * <p>Released publicly as Android 1.6 in September 2009.
+ * <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;
+
+ /**
+ * E.
+ *
+ * <p>Released publicly as Android 2.0 in October 2009.
+ * <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;
+
+ /**
+ * E incremental update.
+ *
+ * <p>Released publicly as Android 2.0.1 in December 2009.
+ */
+ public static final int ECLAIR_0_1 = 6;
+
+ /**
+ * E MR1.
+ *
+ * <p>Released publicly as Android 2.1 in January 2010.
+ */
+ public static final int ECLAIR_MR1 = 7;
+
+ /**
+ * F.
+ *
+ * <p>Released publicly as Android 2.2 in May 2010.
+ */
+ public static final int FROYO = 8;
+
+ /**
+ * G.
+ *
+ * <p>Released publicly as Android 2.3 in December 2010.
+ * <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;
+
+ /**
+ * G MR1.
+ *
+ * <p>Released publicly as Android 2.3.3 in February 2011.
+ */
+ public static final int GINGERBREAD_MR1 = 10;
+
+ /**
+ * H.
+ *
+ * <p>Released publicly as Android 3.0 in February 2011.
+ * <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;
+
+ /**
+ * H MR1.
+ *
+ * <p>Released publicly as Android 3.1 in May 2011.
+ */
+ public static final int HONEYCOMB_MR1 = 12;
+
+ /**
+ * H MR2.
+ *
+ * <p>Released publicly as Android 3.2 in July 2011.
+ * <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;
+
+ /**
+ * I.
+ *
+ * <p>Released publicly as Android 4.0 in October 2011.
+ * <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;
+
+ /**
+ * I MR1.
+ *
+ * <p>Released publicly as Android 4.03 in December 2011.
+ */
+ public static final int ICE_CREAM_SANDWICH_MR1 = 15;
+
+ /**
+ * J.
+ *
+ * <p>Released publicly as Android 4.1 in July 2012.
+ * <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> {@code NfcAdapter.setNdefPushMessage},
+ * {@code NfcAdapter.setNdefPushMessageCallback} and
+ * {@code 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;
+
+ /**
+ * J MR1.
+ *
+ * <p>Released publicly as Android 4.2 in November 2012.
+ * <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;
+
+ /**
+ * J MR2.
+ *
+ * <p>Released publicly as Android 4.3 in July 2013.
+ */
+ public static final int JELLY_BEAN_MR2 = 18;
+
+ /**
+ * K.
+ *
+ * <p>Released publicly as Android 4.4 in October 2013.
+ * <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;
+
+ /**
+ * K for watches.
+ *
+ * <p>Released publicly as Android 4.4W in June 2014.
+ * <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;
+
+ /**
+ * L.
+ *
+ * <p>Released publicly as Android 5.0 in November 2014.
+ * <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;
+
+ /**
+ * L MR1.
+ *
+ * <p>Released publicly as Android 5.1 in March 2015.
+ * <p>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.
+ *
+ * <p>Released publicly as Android 6.0 in October 2015.
+ * <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.
+ *
+ * <p>Released publicly as Android 7.0 in August 2016.
+ * <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.
+ *
+ * <p>Released publicly as Android 7.1 in October 2016.
+ * <p>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>Released publicly as Android 8.0 in August 2017.
+ * <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>Released publicly as Android 8.1 in December 2017.
+ * <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>Released publicly as Android 9 in August 2018.
+ * <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>Released publicly as Android 10 in September 2019.
+ * <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/10">Android 10 overview</a>.</p>
+ * <ul>
+ * <li><a href="/about/versions/10/behavior-changes-all">Behavior changes: all apps</a></li>
+ * <li><a href="/about/versions/10/behavior-changes-10">Behavior changes: apps targeting API
+ * 29+</a></li>
+ * </ul>
+ *
+ */
+ public static final int Q = 29;
+
+ /**
+ * R.
+ *
+ * <p>Released publicly as Android 11 in September 2020.
+ * <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/11">Android 11 overview</a>.</p>
+ * <ul>
+ * <li><a href="/about/versions/11/behavior-changes-all">Behavior changes: all apps</a></li>
+ * <li><a href="/about/versions/11/behavior-changes-11">Behavior changes: Apps targeting
+ * Android 11</a></li>
+ * <li><a href="/about/versions/11/non-sdk-11">Updates to non-SDK interface restrictions
+ * in Android 11</a></li>
+ * </ul>
+ *
+ */
+ public static final int R = 30;
+
+ /**
+ * S.
+ */
+ public static final int S = 31;
+
+ /**
+ * S V2.
+ *
+ * Once more unto the breach, dear friends, once more.
+ */
+ public static final int S_V2 = 32;
+
+ /**
+ * Tiramisu.
+ */
+ public static final int TIRAMISU = 33;
+
+ /**
+ * Upside Down Cake.
+ */
+ public static final int UPSIDE_DOWN_CAKE = 34;
+ }
+
+ /** 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);
+ }
+ }
+ }
+
+ /**
+ * A multiplier for various timeouts on the system.
+ *
+ * The intent is that products targeting software emulators that are orders of magnitude slower
+ * than real hardware may set this to a large number. On real devices and hardware-accelerated
+ * virtualized devices this should not be set.
+ *
+ * @hide
+ */
+ public static final int HW_TIMEOUT_MULTIPLIER =
+ SystemProperties.getInt("ro.hw_timeout_multiplier", 1);
+
+ /**
+ * 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.system.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 = joinListOrElse(
+ TelephonyProperties.baseband_version(), "");
+
+ if (TextUtils.isEmpty(system)) {
+ Slog.e(TAG, "Required ro.system.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";
+ /** @hide */
+ public static final String PARTITION_NAME_BOOTIMAGE = "bootimage";
+ /** @hide */
+ public static final String PARTITION_NAME_ODM = "odm";
+ /** @hide */
+ public static final String PARTITION_NAME_OEM = "oem";
+ /** @hide */
+ public static final String PARTITION_NAME_PRODUCT = "product";
+ /** @hide */
+ public static final String PARTITION_NAME_SYSTEM_EXT = "system_ext";
+ /** @hide */
+ public static final String PARTITION_NAME_VENDOR = "vendor";
+
+ 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(@Nullable 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[] {
+ Partition.PARTITION_NAME_BOOTIMAGE,
+ Partition.PARTITION_NAME_ODM,
+ Partition.PARTITION_NAME_PRODUCT,
+ Partition.PARTITION_NAME_SYSTEM_EXT,
+ Partition.PARTITION_NAME_SYSTEM,
+ Partition.PARTITION_NAME_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 the device is running a debuggable build such as "userdebug" or "eng".
+ *
+ * Debuggable builds allow users to gain root access via local shell, attach debuggers to any
+ * application regardless of whether they have the "debuggable" attribute set, or downgrade
+ * selinux into "permissive" mode in particular.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final boolean IS_DEBUGGABLE =
+ SystemProperties.getInt("ro.debuggable", 0) == 1;
+
+ /**
+ * Returns true if the device is running a debuggable build such as "userdebug" or "eng".
+ *
+ * Debuggable builds allow users to gain root access via local shell, attach debuggers to any
+ * application regardless of whether they have the "debuggable" attribute set, or downgrade
+ * selinux into "permissive" mode in particular.
+ * @hide
+ */
+ @TestApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static boolean isDebuggable() {
+ return IS_DEBUGGABLE;
+ }
+
+ /** {@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 on ARC, the Android Runtime for Chrome
+ * (https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md).
+ * Prior to R this was implemented as a container but from R this will be
+ * a VM. The name of the property remains ro.boot.conntainer as it is
+ * referenced in other projects.
+ *
+ * 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_ARC =
+ 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() {
+ return joinListOrElse(TelephonyProperties.baseband_version(), null);
+ }
+
+ @UnsupportedAppUsage
+ private static String getString(String property) {
+ return SystemProperties.get(property, UNKNOWN);
+ }
+ /**
+ * Return attestation specific proerties.
+ * @param property model, name, brand, device or manufacturer.
+ * @return property value or UNKNOWN
+ */
+ private static String getVendorDeviceIdProperty(String property) {
+ String attestProp = getString(
+ TextUtils.formatSimple("ro.product.%s_for_attestation", property));
+ return attestProp.equals(UNKNOWN)
+ ? getString(TextUtils.formatSimple("ro.product.vendor.%s", 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;
+ }
+ }
+
+ private static <T> String joinListOrElse(List<T> list, String defaultValue) {
+ String ret = list.stream().map(elem -> elem == null ? "" : elem.toString())
+ .collect(Collectors.joining(","));
+ return ret.isEmpty() ? defaultValue : ret;
+ }
+}
diff --git a/android-34/android/os/Bundle.java b/android-34/android/os/Bundle.java
new file mode 100644
index 0000000..e845ffa
--- /dev/null
+++ b/android-34/android/os/Bundle.java
@@ -0,0 +1,1453 @@
+/*
+ * 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 static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.compat.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.
+ *
+ * <p><b>Warning:</b> Note that {@link Bundle} is a lazy container and as such it does NOT implement
+ * {@link #equals(Object)} or {@link #hashCode()}.
+ *
+ * @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;
+
+ /** An unmodifiable {@code Bundle} that is always {@link #isEmpty() empty}. */
+ 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();
+ }
+
+ /**
+ * Constructs a {@link Bundle} containing a copy of {@code from}.
+ *
+ * @param from The bundle to be copied.
+ * @param deep Whether is a deep or shallow copy.
+ *
+ * @hide
+ */
+ Bundle(Bundle from, boolean deep) {
+ super(from, deep);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Make a Bundle for a single key/value pair.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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() {
+ return new Bundle(this, /* deep */ true);
+ }
+
+ /**
+ * 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();
+ mOwnsLazyValues = false;
+ bundle.mOwnsLazyValues = false;
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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) {
+ Parcel p = mParcelledData;
+ mFlags = (Parcel.hasFileDescriptors((p != null) ? p : mMap))
+ ? mFlags | FLAG_HAS_FDS
+ : mFlags & ~FLAG_HAS_FDS;
+ mFlags |= FLAG_HAS_FDS_KNOWN;
+ }
+ return (mFlags & FLAG_HAS_FDS) != 0;
+ }
+
+ /** {@hide} */
+ @Override
+ public void putObject(@Nullable String key, @Nullable Object value) {
+ if (value instanceof Byte) {
+ putByte(key, (Byte) value);
+ } else if (value instanceof Character) {
+ putChar(key, (Character) value);
+ } else if (value instanceof Short) {
+ putShort(key, (Short) value);
+ } else if (value instanceof Float) {
+ putFloat(key, (Float) value);
+ } else if (value instanceof CharSequence) {
+ putCharSequence(key, (CharSequence) value);
+ } else if (value instanceof Parcelable) {
+ putParcelable(key, (Parcelable) value);
+ } else if (value instanceof Size) {
+ putSize(key, (Size) value);
+ } else if (value instanceof SizeF) {
+ putSizeF(key, (SizeF) value);
+ } else if (value instanceof Parcelable[]) {
+ putParcelableArray(key, (Parcelable[]) value);
+ } else if (value instanceof ArrayList) {
+ putParcelableArrayList(key, (ArrayList) value);
+ } else if (value instanceof List) {
+ putParcelableList(key, (List) value);
+ } else if (value instanceof SparseArray) {
+ putSparseParcelableArray(key, (SparseArray) value);
+ } else if (value instanceof Serializable) {
+ putSerializable(key, (Serializable) value);
+ } else if (value instanceof byte[]) {
+ putByteArray(key, (byte[]) value);
+ } else if (value instanceof short[]) {
+ putShortArray(key, (short[]) value);
+ } else if (value instanceof char[]) {
+ putCharArray(key, (char[]) value);
+ } else if (value instanceof float[]) {
+ putFloatArray(key, (float[]) value);
+ } else if (value instanceof CharSequence[]) {
+ putCharSequenceArray(key, (CharSequence[]) value);
+ } else if (value instanceof Bundle) {
+ putBundle(key, (Bundle) value);
+ } else if (value instanceof Binder) {
+ putBinder(key, (Binder) value);
+ } else if (value instanceof IBinder) {
+ putIBinder(key, (IBinder) value);
+ } else {
+ super.putObject(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
+ */
+ @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}
+ *
+ * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+ * {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public <T extends Parcelable> T getParcelable(@Nullable String key) {
+ unparcel();
+ Object o = getValue(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:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <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}.
+ *
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getParcelable(String)} instead.
+ *
+ * @param key a String, or {@code null}
+ * @param clazz The type of the object expected
+ * @return a Parcelable value, or {@code null}
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ return get(key, clazz);
+ }
+
+ /**
+ * 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}
+ *
+ * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from
+ * Android {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public Parcelable[] getParcelableArray(@Nullable String key) {
+ unparcel();
+ Object o = getValue(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:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <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}.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getParcelableArray(String)} instead.
+ *
+ * @param key a String, or {@code null}
+ * @param clazz The type of the items inside the array. This is only verified when unparceling.
+ * @return a Parcelable[] value, or {@code null}
+ */
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> T[] getParcelableArray(@Nullable String key, @NonNull Class<T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ unparcel();
+ try {
+ // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting.
+ return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, clazz.getCanonicalName() + "[]", 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}
+ *
+ * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+ * {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
+ unparcel();
+ Object o = getValue(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 {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <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}.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getParcelableArrayList(String)} instead.
+ *
+ * @param key a String, or {@code null}
+ * @param clazz The type of the items inside the array list. This is only verified when
+ * unparceling.
+ * @return an ArrayList<T> value, or {@code null}
+ */
+ @SuppressLint("NullableCollection")
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> ArrayList<T> getParcelableArrayList(@Nullable String key,
+ @NonNull Class<? extends T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ return getArrayList(key, clazz);
+ }
+
+ /**
+ * 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
+ *
+ * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from
+ * Android {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
+ unparcel();
+ Object o = getValue(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 {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #getSparseParcelableArray(String)} instead.
+ *
+ * @param key a String, or null
+ * @param clazz The type of the items inside the sparse array. This is only verified when
+ * unparceling.
+ * @return a SparseArray of T values, or null
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> SparseArray<T> getSparseParcelableArray(@Nullable String key,
+ @NonNull Class<? extends T> clazz) {
+ // The reason for not using <T extends Parcelable> is because the caller could provide a
+ // super class to restrict the children that doesn't implement Parcelable itself while the
+ // children do, more details at b/210800751 (same reasoning applies here).
+ unparcel();
+ try {
+ return (SparseArray<T>) getValue(key, SparseArray.class, requireNonNull(clazz));
+ } catch (ClassCastException | BadTypeParcelableException e) {
+ typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", 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
+ *
+ * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android
+ * {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Override
+ @Nullable
+ public Serializable getSerializable(@Nullable String key) {
+ return super.getSerializable(key);
+ }
+
+ /**
+ * Returns the value associated with the given key, or {@code null} if:
+ * <ul>
+ * <li>No mapping of the desired type exists for the given key.
+ * <li>A {@code null} value is explicitly associated with the key.
+ * <li>The object is not of type {@code clazz}.
+ * </ul>
+ *
+ * @param key a String, or null
+ * @param clazz The expected class of the returned type
+ * @return a Serializable value, or null
+ */
+ @Nullable
+ public <T extends Serializable> T getSerializable(@Nullable String key,
+ @NonNull Class<T> clazz) {
+ return super.getSerializable(key, requireNonNull(clazz));
+ }
+
+ /**
+ * 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 {
+ 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) {
+ readFromParcelInner(parcel);
+ mFlags = FLAG_ALLOW_FDS;
+ maybePrefillHasFds();
+ }
+
+ /**
+ * Returns a string representation of the {@link Bundle} that may be suitable for debugging. It
+ * won't print the internal map if its content hasn't been unparcelled.
+ */
+ @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 dumpDebug(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-34/android/os/BundleMerger.java b/android-34/android/os/BundleMerger.java
new file mode 100644
index 0000000..857aaf5
--- /dev/null
+++ b/android-34/android/os/BundleMerger.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.function.BinaryOperator;
+
+/**
+ * Configured rules for merging two {@link Bundle} instances.
+ * <p>
+ * By default, values from both {@link Bundle} instances are blended together on
+ * a key-wise basis, and conflicting value definitions for a key are dropped.
+ * <p>
+ * Nuanced strategies for handling conflicting value definitions can be applied
+ * using {@link #setMergeStrategy(String, int)} and
+ * {@link #setDefaultMergeStrategy(int)}.
+ * <p>
+ * When conflicting values have <em>inconsistent</em> data types (such as trying
+ * to merge a {@link String} and a {@link Integer}), both conflicting values are
+ * rejected and the key becomes undefined, regardless of the requested strategy.
+ *
+ * @hide
+ */
+public class BundleMerger implements Parcelable {
+ private static final String TAG = "BundleMerger";
+
+ private @Strategy int mDefaultStrategy = STRATEGY_REJECT;
+
+ private final ArrayMap<String, Integer> mStrategies = new ArrayMap<>();
+
+ /**
+ * Merge strategy that rejects both conflicting values.
+ */
+ public static final int STRATEGY_REJECT = 0;
+
+ /**
+ * Merge strategy that selects the first of conflicting values.
+ */
+ public static final int STRATEGY_FIRST = 1;
+
+ /**
+ * Merge strategy that selects the last of conflicting values.
+ */
+ public static final int STRATEGY_LAST = 2;
+
+ /**
+ * Merge strategy that selects the "minimum" of conflicting values which are
+ * {@link Comparable} with each other.
+ */
+ public static final int STRATEGY_COMPARABLE_MIN = 3;
+
+ /**
+ * Merge strategy that selects the "maximum" of conflicting values which are
+ * {@link Comparable} with each other.
+ */
+ public static final int STRATEGY_COMPARABLE_MAX = 4;
+
+ /**
+ * Merge strategy that numerically adds both conflicting values.
+ */
+ public static final int STRATEGY_NUMBER_ADD = 10;
+
+ /**
+ * Merge strategy that numerically increments the first conflicting value by
+ * {@code 1} and ignores the last conflicting value.
+ */
+ public static final int STRATEGY_NUMBER_INCREMENT_FIRST = 20;
+
+ /**
+ * Merge strategy that numerically increments the first conflicting value by
+ * {@code 1} and also numerically adds both conflicting values.
+ */
+ public static final int STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD = 25;
+
+ /**
+ * Merge strategy that combines conflicting values using a boolean "and"
+ * operation.
+ */
+ public static final int STRATEGY_BOOLEAN_AND = 30;
+
+ /**
+ * Merge strategy that combines conflicting values using a boolean "or"
+ * operation.
+ */
+ public static final int STRATEGY_BOOLEAN_OR = 40;
+
+ /**
+ * Merge strategy that combines two conflicting array values by appending
+ * the last array after the first array.
+ */
+ public static final int STRATEGY_ARRAY_APPEND = 50;
+
+ /**
+ * Merge strategy that combines two conflicting {@link ArrayList} values by
+ * appending the last {@link ArrayList} after the first {@link ArrayList}.
+ */
+ public static final int STRATEGY_ARRAY_LIST_APPEND = 60;
+
+ @IntDef(flag = false, prefix = { "STRATEGY_" }, value = {
+ STRATEGY_REJECT,
+ STRATEGY_FIRST,
+ STRATEGY_LAST,
+ STRATEGY_COMPARABLE_MIN,
+ STRATEGY_COMPARABLE_MAX,
+ STRATEGY_NUMBER_ADD,
+ STRATEGY_NUMBER_INCREMENT_FIRST,
+ STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD,
+ STRATEGY_BOOLEAN_AND,
+ STRATEGY_BOOLEAN_OR,
+ STRATEGY_ARRAY_APPEND,
+ STRATEGY_ARRAY_LIST_APPEND,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Strategy {}
+
+ /**
+ * Create a empty set of rules for merging two {@link Bundle} instances.
+ */
+ public BundleMerger() {
+ }
+
+ private BundleMerger(@NonNull Parcel in) {
+ mDefaultStrategy = in.readInt();
+ final int N = in.readInt();
+ for (int i = 0; i < N; i++) {
+ mStrategies.put(in.readString(), in.readInt());
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mDefaultStrategy);
+ final int N = mStrategies.size();
+ out.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ out.writeString(mStrategies.keyAt(i));
+ out.writeInt(mStrategies.valueAt(i));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Configure the default merge strategy to be used when there isn't a
+ * more-specific strategy defined for a particular key via
+ * {@link #setMergeStrategy(String, int)}.
+ */
+ public void setDefaultMergeStrategy(@Strategy int strategy) {
+ mDefaultStrategy = strategy;
+ }
+
+ /**
+ * Configure the merge strategy to be used for the given key.
+ * <p>
+ * Subsequent calls for the same key will overwrite any previously
+ * configured strategy.
+ */
+ public void setMergeStrategy(@NonNull String key, @Strategy int strategy) {
+ mStrategies.put(key, strategy);
+ }
+
+ /**
+ * Return the merge strategy to be used for the given key, as defined by
+ * {@link #setMergeStrategy(String, int)}.
+ * <p>
+ * If no specific strategy has been configured for the given key, this
+ * returns {@link #setDefaultMergeStrategy(int)}.
+ */
+ public @Strategy int getMergeStrategy(@NonNull String key) {
+ return (int) mStrategies.getOrDefault(key, mDefaultStrategy);
+ }
+
+ /**
+ * Return a {@link BinaryOperator} which applies the strategies configured
+ * in this object to merge the two given {@link Bundle} arguments.
+ */
+ public BinaryOperator<Bundle> asBinaryOperator() {
+ return this::merge;
+ }
+
+ /**
+ * Apply the strategies configured in this object to merge the two given
+ * {@link Bundle} arguments.
+ *
+ * @return the merged {@link Bundle} result. If one argument is {@code null}
+ * it will return the other argument. If both arguments are null it
+ * will return {@code null}.
+ */
+ @SuppressWarnings("deprecation")
+ public @Nullable Bundle merge(@Nullable Bundle first, @Nullable Bundle last) {
+ if (first == null && last == null) {
+ return null;
+ }
+ if (first == null) {
+ first = Bundle.EMPTY;
+ }
+ if (last == null) {
+ last = Bundle.EMPTY;
+ }
+
+ // Start by bulk-copying all values without attempting to unpack any
+ // custom parcelables; we'll circle back to handle conflicts below
+ final Bundle res = new Bundle();
+ res.putAll(first);
+ res.putAll(last);
+
+ final ArraySet<String> conflictingKeys = new ArraySet<>();
+ conflictingKeys.addAll(first.keySet());
+ conflictingKeys.retainAll(last.keySet());
+ for (int i = 0; i < conflictingKeys.size(); i++) {
+ final String key = conflictingKeys.valueAt(i);
+ final int strategy = getMergeStrategy(key);
+ final Object firstValue = first.get(key);
+ final Object lastValue = last.get(key);
+ try {
+ res.putObject(key, merge(strategy, firstValue, lastValue));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to merge key " + key + " with " + firstValue + " and "
+ + lastValue + " using strategy " + strategy, e);
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Merge the two given values. If only one of the values is defined, it
+ * always wins, otherwise the given strategy is applied.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static @Nullable Object merge(@Strategy int strategy,
+ @Nullable Object first, @Nullable Object last) {
+ if (first == null) return last;
+ if (last == null) return first;
+
+ if (first.getClass() != last.getClass()) {
+ throw new IllegalArgumentException("Merging requires consistent classes; first "
+ + first.getClass() + " last " + last.getClass());
+ }
+
+ switch (strategy) {
+ case STRATEGY_REJECT:
+ // Only actually reject when the values are different
+ if (Objects.deepEquals(first, last)) {
+ return first;
+ } else {
+ return null;
+ }
+ case STRATEGY_FIRST:
+ return first;
+ case STRATEGY_LAST:
+ return last;
+ case STRATEGY_COMPARABLE_MIN:
+ return comparableMin(first, last);
+ case STRATEGY_COMPARABLE_MAX:
+ return comparableMax(first, last);
+ case STRATEGY_NUMBER_ADD:
+ return numberAdd(first, last);
+ case STRATEGY_NUMBER_INCREMENT_FIRST:
+ return numberIncrementFirst(first, last);
+ case STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD:
+ return numberAdd(numberIncrementFirst(first, last), last);
+ case STRATEGY_BOOLEAN_AND:
+ return booleanAnd(first, last);
+ case STRATEGY_BOOLEAN_OR:
+ return booleanOr(first, last);
+ case STRATEGY_ARRAY_APPEND:
+ return arrayAppend(first, last);
+ case STRATEGY_ARRAY_LIST_APPEND:
+ return arrayListAppend(first, last);
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object comparableMin(@NonNull Object first, @NonNull Object last) {
+ return ((Comparable<Object>) first).compareTo(last) < 0 ? first : last;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object comparableMax(@NonNull Object first, @NonNull Object last) {
+ return ((Comparable<Object>) first).compareTo(last) >= 0 ? first : last;
+ }
+
+ private static @NonNull Object numberAdd(@NonNull Object first, @NonNull Object last) {
+ if (first instanceof Integer) {
+ return ((Integer) first) + ((Integer) last);
+ } else if (first instanceof Long) {
+ return ((Long) first) + ((Long) last);
+ } else if (first instanceof Float) {
+ return ((Float) first) + ((Float) last);
+ } else if (first instanceof Double) {
+ return ((Double) first) + ((Double) last);
+ } else {
+ throw new IllegalArgumentException("Unable to add " + first.getClass());
+ }
+ }
+
+ private static @NonNull Number numberIncrementFirst(@NonNull Object first,
+ @NonNull Object last) {
+ if (first instanceof Integer) {
+ return ((Integer) first) + 1;
+ } else if (first instanceof Long) {
+ return ((Long) first) + 1L;
+ } else {
+ throw new IllegalArgumentException("Unable to add " + first.getClass());
+ }
+ }
+
+ private static @NonNull Object booleanAnd(@NonNull Object first, @NonNull Object last) {
+ return ((Boolean) first) && ((Boolean) last);
+ }
+
+ private static @NonNull Object booleanOr(@NonNull Object first, @NonNull Object last) {
+ return ((Boolean) first) || ((Boolean) last);
+ }
+
+ private static @NonNull Object arrayAppend(@NonNull Object first, @NonNull Object last) {
+ if (!first.getClass().isArray()) {
+ throw new IllegalArgumentException("Unable to append " + first.getClass());
+ }
+ final Class<?> clazz = first.getClass().getComponentType();
+ final int firstLength = Array.getLength(first);
+ final int lastLength = Array.getLength(last);
+ final Object res = Array.newInstance(clazz, firstLength + lastLength);
+ System.arraycopy(first, 0, res, 0, firstLength);
+ System.arraycopy(last, 0, res, firstLength, lastLength);
+ return res;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object arrayListAppend(@NonNull Object first, @NonNull Object last) {
+ if (!(first instanceof ArrayList)) {
+ throw new IllegalArgumentException("Unable to append " + first.getClass());
+ }
+ final ArrayList<Object> firstList = (ArrayList<Object>) first;
+ final ArrayList<Object> lastList = (ArrayList<Object>) last;
+ final ArrayList<Object> res = new ArrayList<>(firstList.size() + lastList.size());
+ res.addAll(firstList);
+ res.addAll(lastList);
+ return res;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<BundleMerger> CREATOR =
+ new Parcelable.Creator<BundleMerger>() {
+ @Override
+ public BundleMerger createFromParcel(Parcel in) {
+ return new BundleMerger(in);
+ }
+
+ @Override
+ public BundleMerger[] newArray(int size) {
+ return new BundleMerger[size];
+ }
+ };
+}
diff --git a/android-34/android/os/CancellationSignal.java b/android-34/android/os/CancellationSignal.java
new file mode 100644
index 0000000..260f7ad
--- /dev/null
+++ b/android-34/android/os/CancellationSignal.java
@@ -0,0 +1,214 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Provides the ability to cancel an operation in progress.
+ */
+public final class CancellationSignal {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private boolean mIsCanceled;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private OnCancelListener mOnCancelListener;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private ICancellationSignal mRemote;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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) {
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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-34/android/os/CancellationSignalBeamer.java b/android-34/android/os/CancellationSignalBeamer.java
new file mode 100644
index 0000000..5c0b221
--- /dev/null
+++ b/android-34/android/os/CancellationSignalBeamer.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SuppressLint;
+import android.system.SystemCleaner;
+import android.util.Pair;
+import android.view.inputmethod.CancellableHandwritingGesture;
+import android.view.inputmethod.HandwritingGesture;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transport for {@link CancellationSignal}, but unlike
+ * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the
+ * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable
+ * request.
+ *
+ * <p><strong>Important:</strong> For this to work, the following invariants must be held up:
+ * <ul>
+ * <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result
+ * (otherwise, the token will be leaked and cancellation isn't propagated), and that call
+ * must happen after the call using the
+ * token is sent (otherwise, any concurrent cancellation may be lost). It is strongly
+ * recommended to use try-with-resources on the token.
+ * <li>The cancel(), forget() and cancellable operations transporting the token must either
+ * all be oneway on the same binder, or all be non-oneway to guarantee proper ordering.
+ * <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there
+ * can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}.
+ *
+ * </ul>
+ * <p>Caveats:
+ * <ul>
+ * <li>Cancellation is only ever dispatched after the token is closed, and thus after the
+ * call performing the cancellable operation (if the invariants are followed). The operation
+ * must therefore not block the incoming binder thread, or cancellation won't be possible.
+ * <li>Consequently, in the unlikely event that the sender dies right after beaming an already
+ * cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with
+ * {@link CancellationSignal#createTransport()}).
+ * <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources
+ * / when closing the token. If the receiver is in the same process, and the signal is
+ * already cancelled, this may invoke the target's OnCancelListener during that phase.
+ * </ul>
+ *
+ *
+ * <p>Usage:
+ * <pre>
+ * // Sender:
+ *
+ * class FooManager {
+ * var mCancellationSignalSender = new CancellationSignalBeamer.Sender() {
+ * @Override
+ * public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
+ *
+ * @Override
+ * public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); }
+ * };
+ *
+ * public void doCancellableOperation(..., CancellationSignal cs) {
+ * try (var csToken = mCancellationSignalSender.beam(cs)) {
+ * remoteIFooService.doCancellableOperation(..., csToken);
+ * }
+ * }
+ * }
+ *
+ * // Receiver:
+ *
+ * class FooManagerService extends IFooService.Stub {
+ * var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver();
+ *
+ * @Override
+ * public void doCancellableOperation(..., IBinder csToken) {
+ * CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
+ * // ...
+ * }
+ *
+ * @Override
+ * public void onCancelToken(..., IBinder csToken) {
+ * mCancellationSignalReceiver.cancelToken(csToken))
+ * }
+ *
+ * @Override
+ * public void onForgetToken(..., IBinder csToken) {
+ * mCancellationSignalReceiver.forgetToken(csToken))
+ * }
+ * }
+ *
+ * </pre>
+ *
+ * @hide
+ */
+public class CancellationSignalBeamer {
+
+ static final Cleaner sCleaner = SystemCleaner.cleaner();
+
+ /** The sending side of an {@link CancellationSignalBeamer} */
+ public abstract static class Sender {
+
+ /**
+ * Beams a {@link CancellationSignal} through an existing Binder interface.
+ *
+ * @param cs the {@code CancellationSignal} to beam, or {@code null}.
+ * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em>
+ * the binder call transporting it to the remote process, best with
+ * try-with-resources. {@code null} if {@code cs} was {@code null}.
+ */
+ // TODO(b/254888024): @MustBeClosed
+ @Nullable
+ public CloseableToken beam(@Nullable CancellationSignal cs) {
+ if (cs == null) {
+ return null;
+ }
+ return new Token(this, cs);
+ }
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} was closed.
+ *
+ * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See
+ * {@link CancellationSignalBeamer} for details.
+ */
+ public abstract void onCancel(@NonNull IBinder token);
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} was GC'd.
+ *
+ * MUST be forwarded to {@link Receiver#forget} with proper ordering. See
+ * {@link CancellationSignalBeamer} for details.
+ */
+ public abstract void onForget(@NonNull IBinder token);
+
+ private static final ThreadLocal<Pair<Sender, ArrayList<CloseableToken>>> sScope =
+ new ThreadLocal<>();
+
+ /**
+ * Beams a {@link CancellationSignal} through an existing Binder interface.
+ * @param gesture {@link HandwritingGesture} that supports
+ * {@link CancellableHandwritingGesture cancellation} requesting cancellation token.
+ * @return {@link IBinder} token. MUST be {@link MustClose#close}d <em>after</em>
+ * the binder call transporting it to the remote process, best with
+ * try-with-resources. {@code null} if {@code cs} was {@code null} or if
+ * {@link HandwritingGesture} isn't {@link CancellableHandwritingGesture cancellable}.
+ */
+ @NonNull
+ public MustClose beamScopeIfNeeded(@NonNull HandwritingGesture gesture) {
+ if (!(gesture instanceof CancellableHandwritingGesture)) {
+ return null;
+ }
+ sScope.set(Pair.create(this, new ArrayList<>()));
+ return () -> {
+ var tokens = sScope.get().second;
+ sScope.remove();
+ for (int i = tokens.size() - 1; i >= 0; i--) {
+ if (tokens.get(i) != null) {
+ tokens.get(i).close();
+ }
+ }
+ };
+ }
+
+ /**
+ * An {@link AutoCloseable} interface with {@link AutoCloseable#close()} callback.
+ */
+ public interface MustClose extends AutoCloseable {
+ @Override
+ void close();
+ }
+
+ /**
+ * Beams a {@link CancellationSignal} token from existing scope created by previous call to
+ * {@link #beamScopeIfNeeded()}
+ * @param cs {@link CancellationSignal} for which token should be returned.
+ * @return {@link IBinder} token.
+ */
+ @NonNull
+ public static IBinder beamFromScope(@NonNull CancellationSignal cs) {
+ var state = sScope.get();
+ if (state != null) {
+ var token = state.first.beam(cs);
+ state.second.add(token);
+ return token;
+ }
+ return null;
+ }
+
+ private static class Token extends Binder implements CloseableToken, Runnable {
+
+ private final Sender mSender;
+ private Preparer mPreparer;
+
+ private Token(Sender sender, CancellationSignal signal) {
+ mSender = sender;
+ mPreparer = new Preparer(sender, signal, this);
+ }
+
+ @Override
+ public void close() {
+ Preparer preparer = mPreparer;
+ mPreparer = null;
+ if (preparer != null) {
+ preparer.setup();
+ }
+ }
+
+ @Override
+ public void run() {
+ mSender.onForget(this);
+ }
+
+ private static class Preparer implements CancellationSignal.OnCancelListener {
+ private final Sender mSender;
+ private final CancellationSignal mSignal;
+ private final Token mToken;
+
+ private Preparer(Sender sender, CancellationSignal signal, Token token) {
+ mSender = sender;
+ mSignal = signal;
+ mToken = token;
+ }
+
+ void setup() {
+ sCleaner.register(this, mToken);
+ mSignal.setOnCancelListener(this);
+ }
+
+ @Override
+ public void onCancel() {
+ try {
+ mSender.onCancel(mToken);
+ } finally {
+ // Make sure we dispatch onCancel before the cleaner can run.
+ Reference.reachabilityFence(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder.
+ *
+ * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
+ */
+ public interface CloseableToken extends IBinder, MustClose {
+ @Override
+ void close(); // No throws
+ }
+ }
+
+ /** The receiving side of a {@link CancellationSignalBeamer}. */
+ public static class Receiver implements IBinder.DeathRecipient {
+ private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>();
+ private final boolean mCancelOnSenderDeath;
+
+ /**
+ * Constructs a new {@code Receiver}.
+ *
+ * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
+ * {@link #unbeam} are automatically {@link #cancel}led if the sender token
+ * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+ * sending process drops all references to the {@link CancellationSignal} before
+ * process death, the cancellation is not guaranteed.
+ */
+ public Receiver(boolean cancelOnSenderDeath) {
+ mCancelOnSenderDeath = cancelOnSenderDeath;
+ }
+
+ /**
+ * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a
+ * {@link CancellationSignal}.
+ *
+ * A subsequent call to {@link #cancel} with the same token will cancel the returned
+ * {@code CancellationSignal}.
+ *
+ * @param token a token that was obtained from {@link Sender}, possibly in a remote process.
+ * @return a {@link CancellationSignal} linked to the given token.
+ */
+ @Nullable
+ @SuppressLint("VisiblySynchronized")
+ public CancellationSignal unbeam(@Nullable IBinder token) {
+ if (token == null) {
+ return null;
+ }
+ synchronized (this) {
+ CancellationSignal cs = mTokenMap.get(token);
+ if (cs != null) {
+ return cs;
+ }
+
+ cs = new CancellationSignal();
+ mTokenMap.put(token, cs);
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ dead(token);
+ }
+ return cs;
+ }
+ }
+
+ /**
+ * Forgets state associated with the given token (if any).
+ *
+ * Subsequent calls to {@link #cancel} or binder death notifications on the token will not
+ * have any effect.
+ *
+ * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and
+ * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+ *
+ * Optionally, the receiving service logic may also invoke this if it can guarantee that
+ * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+ * using the CancellationSignal has been fully completed).
+ *
+ * @param token the token to forget. No-op if {@code null}.
+ */
+ @SuppressLint("VisiblySynchronized")
+ public void forget(@Nullable IBinder token) {
+ synchronized (this) {
+ if (mTokenMap.remove(token) != null) {
+ token.unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ /**
+ * Cancels the {@link CancellationSignal} associated with the given token (if any).
+ *
+ * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and
+ * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+ *
+ * Optionally, the receiving service logic may also invoke this if it can guarantee that
+ * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+ * using the CancellationSignal has been fully completed).
+ *
+ * @param token the token to forget. No-op if {@code null}.
+ */
+ @SuppressLint("VisiblySynchronized")
+ public void cancel(@Nullable IBinder token) {
+ CancellationSignal cs;
+ synchronized (this) {
+ cs = mTokenMap.get(token);
+ if (cs != null) {
+ forget(token);
+ } else {
+ return;
+ }
+ }
+ cs.cancel();
+ }
+
+ private void dead(@NonNull IBinder token) {
+ if (mCancelOnSenderDeath) {
+ cancel(token);
+ } else {
+ forget(token);
+ }
+ }
+
+ @Override
+ public void binderDied(@NonNull IBinder who) {
+ dead(who);
+ }
+
+ @Override
+ public void binderDied() {
+ throw new RuntimeException("unreachable");
+ }
+ }
+}
diff --git a/android-34/android/os/CarrierAssociatedAppEntry.java b/android-34/android/os/CarrierAssociatedAppEntry.java
new file mode 100644
index 0000000..13f6eb6
--- /dev/null
+++ b/android-34/android/os/CarrierAssociatedAppEntry.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+/**
+ * Represents a carrier app entry for use with {@link SystemConfigService}.
+ *
+ * @hide
+ */
+public final class CarrierAssociatedAppEntry implements Parcelable {
+
+ /**
+ * For carrier-associated app entries that don't specify the addedInSdk XML
+ * attribute.
+ */
+ public static final int SDK_UNSPECIFIED = -1;
+
+ public final String packageName;
+ /** May be {@link #SDK_UNSPECIFIED}. */
+ public final int addedInSdk;
+
+ public CarrierAssociatedAppEntry(String packageName, int addedInSdk) {
+ this.packageName = packageName;
+ this.addedInSdk = addedInSdk;
+ }
+
+ public CarrierAssociatedAppEntry(Parcel in) {
+ packageName = in.readString();
+ addedInSdk = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(packageName);
+ dest.writeInt(addedInSdk);
+ }
+
+ public static final Parcelable.Creator<CarrierAssociatedAppEntry> CREATOR =
+ new Parcelable.Creator<CarrierAssociatedAppEntry>() {
+ @Override
+ public CarrierAssociatedAppEntry createFromParcel(Parcel source) {
+ return new CarrierAssociatedAppEntry(source);
+ }
+
+ @Override
+ public CarrierAssociatedAppEntry[] newArray(int size) {
+ return new CarrierAssociatedAppEntry[size];
+ }
+ };
+}
diff --git a/android-34/android/os/ChildZygoteProcess.java b/android-34/android/os/ChildZygoteProcess.java
new file mode 100644
index 0000000..337a3e2
--- /dev/null
+++ b/android-34/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-34/android/os/CombinedVibration.java b/android-34/android/os/CombinedVibration.java
new file mode 100644
index 0000000..5f2c113
--- /dev/null
+++ b/android-34/android/os/CombinedVibration.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.TestApi;
+import android.util.SparseArray;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A CombinedVibration describes a combination of haptic effects to be performed by one or more
+ * {@link Vibrator Vibrators}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ *
+ * @see VibrationEffect
+ */
+@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
+public abstract class CombinedVibration implements Parcelable {
+ private static final int PARCEL_TOKEN_MONO = 1;
+ private static final int PARCEL_TOKEN_STEREO = 2;
+ private static final int PARCEL_TOKEN_SEQUENTIAL = 3;
+
+ /** Prevent subclassing from outside of the framework. */
+ CombinedVibration() {
+ }
+
+ /**
+ * Create a vibration that plays a single effect in parallel on all vibrators.
+ *
+ * A parallel vibration that takes a single {@link VibrationEffect} to be performed by multiple
+ * vibrators at the same time.
+ *
+ * @param effect The {@link VibrationEffect} to perform.
+ * @return The combined vibration representing the single effect to be played in all vibrators.
+ */
+ @NonNull
+ public static CombinedVibration createParallel(@NonNull VibrationEffect effect) {
+ CombinedVibration combined = new Mono(effect);
+ combined.validate();
+ return combined;
+ }
+
+ /**
+ * Start creating a vibration that plays effects in parallel on one or more vibrators.
+ *
+ * A parallel vibration takes one or more {@link VibrationEffect VibrationEffects} associated to
+ * individual vibrators to be performed at the same time.
+ *
+ * @see CombinedVibration.ParallelCombination
+ */
+ @NonNull
+ public static ParallelCombination startParallel() {
+ return new ParallelCombination();
+ }
+
+ /**
+ * Start creating a vibration that plays effects in sequence on one or more vibrators.
+ *
+ * A sequential vibration takes one or more {@link CombinedVibration CombinedVibrations} to be
+ * performed by one or more vibrators in order. Each {@link CombinedVibration} starts only after
+ * the previous one is finished.
+ *
+ * @hide
+ * @see CombinedVibration.SequentialCombination
+ */
+ @TestApi
+ @NonNull
+ public static SequentialCombination startSequential() {
+ return new SequentialCombination();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Gets the estimated duration of the combined vibration in milliseconds.
+ *
+ * <p>For parallel combinations this means the maximum duration of any individual {@link
+ * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
+ *
+ * <p>For combinations of 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();
+
+ /**
+ * Returns true if this effect could represent a touch haptic feedback.
+ *
+ * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified
+ * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN,
+ * then this method will be used to classify the most common use case and make sure they are
+ * covered by the user settings for "Touch feedback".
+ *
+ * @hide
+ */
+ public boolean isHapticFeedbackCandidate() {
+ return false;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /** @hide */
+ public abstract boolean hasVibrator(int vibratorId);
+
+ /**
+ * A combination of haptic effects that should be played in multiple vibrators in parallel.
+ *
+ * @see CombinedVibration#startParallel()
+ */
+ public static final class ParallelCombination {
+
+ private final SparseArray<VibrationEffect> mEffects = new SparseArray<>();
+
+ ParallelCombination() {
+ }
+
+ /**
+ * Add or replace a one shot vibration effect to be performed by the specified vibrator.
+ *
+ * @param vibratorId The id of the vibrator that should perform this effect.
+ * @param effect The effect this vibrator should play.
+ * @return The {@link ParallelCombination} object to enable adding
+ * multiple effects in one chain.
+ * @see VibrationEffect#createOneShot(long, int)
+ */
+ @NonNull
+ public ParallelCombination addVibrator(int vibratorId, @NonNull VibrationEffect effect) {
+ mEffects.put(vibratorId, effect);
+ return this;
+ }
+
+ /**
+ * Combine all of the added effects into a {@link CombinedVibration}.
+ *
+ * The {@link ParallelCombination} object is still valid after this
+ * call, so you can continue adding more effects to it and generating more
+ * {@link CombinedVibration}s by calling this method again.
+ *
+ * @return The {@link CombinedVibration} resulting from combining the added effects to
+ * be played in parallel.
+ */
+ @NonNull
+ public CombinedVibration combine() {
+ if (mEffects.size() == 0) {
+ throw new IllegalStateException(
+ "Combination must have at least one element to combine.");
+ }
+ CombinedVibration combined = new Stereo(mEffects);
+ combined.validate();
+ return combined;
+ }
+ }
+
+ /**
+ * A combination of haptic effects that should be played in multiple vibrators in sequence.
+ *
+ * @hide
+ * @see CombinedVibration#startSequential()
+ */
+ @TestApi
+ public static final class SequentialCombination {
+
+ private final ArrayList<CombinedVibration> mEffects = new ArrayList<>();
+ private final ArrayList<Integer> mDelays = new ArrayList<>();
+
+ SequentialCombination() {
+ }
+
+ /**
+ * Add a single vibration effect to be performed next.
+ *
+ * Similar to {@link #addNext(int, VibrationEffect, int)}, but with no delay. The effect
+ * will start playing immediately after the previous vibration is finished.
+ *
+ * @param vibratorId The id of the vibrator that should perform this effect.
+ * @param effect The effect this vibrator should play.
+ * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
+ * multiple effects in one chain.
+ */
+ @NonNull
+ public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect) {
+ return addNext(vibratorId, effect, /* delay= */ 0);
+ }
+
+ /**
+ * Add a single vibration effect to be performed next.
+ *
+ * The delay is applied immediately after the previous vibration is finished. The effect
+ * will start playing after the delay.
+ *
+ * @param vibratorId The id of the vibrator that should perform this effect.
+ * @param effect The effect this vibrator should play.
+ * @param delay The amount of time, in milliseconds, to wait between playing the prior
+ * vibration and this one, starting at the time the previous vibration in
+ * this sequence is finished.
+ * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
+ * multiple effects in one chain.
+ */
+ @NonNull
+ public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect,
+ int delay) {
+ return addNext(
+ CombinedVibration.startParallel().addVibrator(vibratorId, effect).combine(),
+ delay);
+ }
+
+ /**
+ * Add a combined vibration effect to be performed next.
+ *
+ * Similar to {@link #addNext(CombinedVibration, int)}, but with no delay. The effect will
+ * start playing immediately after the previous vibration is finished.
+ *
+ * @param effect The combined effect to be performed next.
+ * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
+ * multiple effects in one chain.
+ * @see VibrationEffect#createOneShot(long, int)
+ */
+ @NonNull
+ public SequentialCombination addNext(@NonNull CombinedVibration effect) {
+ return addNext(effect, /* delay= */ 0);
+ }
+
+ /**
+ * Add a combined vibration effect to be performed next.
+ *
+ * The delay is applied immediately after the previous vibration is finished. The vibration
+ * will start playing after the delay.
+ *
+ * @param effect The combined effect to be performed next.
+ * @param delay The amount of time, in milliseconds, to wait between playing the prior
+ * vibration and this one, starting at the time the previous vibration in this
+ * sequence is finished.
+ * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
+ * multiple effects in one chain.
+ */
+ @NonNull
+ public SequentialCombination addNext(@NonNull CombinedVibration effect, int delay) {
+ if (effect instanceof Sequential) {
+ Sequential sequentialEffect = (Sequential) effect;
+ int firstEffectIndex = mDelays.size();
+ mEffects.addAll(sequentialEffect.getEffects());
+ mDelays.addAll(sequentialEffect.getDelays());
+ mDelays.set(firstEffectIndex, delay + mDelays.get(firstEffectIndex));
+ } else {
+ mEffects.add(effect);
+ mDelays.add(delay);
+ }
+ return this;
+ }
+
+ /**
+ * Combine all of the added effects in sequence.
+ *
+ * The {@link CombinedVibration.SequentialCombination} object is still valid after
+ * this call, so you can continue adding more effects to it and generating more {@link
+ * CombinedVibration}s by calling this method again.
+ *
+ * @return The {@link CombinedVibration} resulting from combining the added effects to
+ * be played in sequence.
+ */
+ @NonNull
+ public CombinedVibration combine() {
+ if (mEffects.size() == 0) {
+ throw new IllegalStateException(
+ "Combination must have at least one element to combine.");
+ }
+ CombinedVibration combined = new Sequential(mEffects, mDelays);
+ combined.validate();
+ return combined;
+ }
+ }
+
+ /**
+ * Represents a single {@link VibrationEffect} that should be played in all vibrators at the
+ * same time.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Mono extends CombinedVibration {
+ private final VibrationEffect mEffect;
+
+ Mono(Parcel in) {
+ mEffect = VibrationEffect.CREATOR.createFromParcel(in);
+ }
+
+ Mono(@NonNull VibrationEffect effect) {
+ mEffect = effect;
+ }
+
+ @NonNull
+ public VibrationEffect getEffect() {
+ return mEffect;
+ }
+
+ @Override
+ public long getDuration() {
+ return mEffect.getDuration();
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return mEffect.isHapticFeedbackCandidate();
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ mEffect.validate();
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasVibrator(int vibratorId) {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Mono)) {
+ return false;
+ }
+ Mono other = (Mono) o;
+ return mEffect.equals(other.mEffect);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEffect);
+ }
+
+ @Override
+ public String toString() {
+ return "Mono{mEffect=" + mEffect + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_MONO);
+ mEffect.writeToParcel(out, flags);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<Mono> CREATOR =
+ new Parcelable.Creator<Mono>() {
+ @Override
+ public Mono createFromParcel(@NonNull Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Mono(in);
+ }
+
+ @Override
+ @NonNull
+ public Mono[] newArray(int size) {
+ return new Mono[size];
+ }
+ };
+ }
+
+ /**
+ * Represents a set of {@link VibrationEffect VibrationEffects} associated to individual
+ * vibrators that should be played at the same time.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Stereo extends CombinedVibration {
+
+ /** Mapping vibrator ids to effects. */
+ private final SparseArray<VibrationEffect> mEffects;
+
+ Stereo(Parcel in) {
+ int size = in.readInt();
+ mEffects = new SparseArray<>(size);
+ for (int i = 0; i < size; i++) {
+ int vibratorId = in.readInt();
+ mEffects.put(vibratorId, VibrationEffect.CREATOR.createFromParcel(in));
+ }
+ }
+
+ Stereo(@NonNull SparseArray<VibrationEffect> effects) {
+ mEffects = new SparseArray<>(effects.size());
+ for (int i = 0; i < effects.size(); i++) {
+ mEffects.put(effects.keyAt(i), effects.valueAt(i));
+ }
+ }
+
+ /** Effects to be performed in parallel, where each key represents the vibrator id. */
+ @NonNull
+ public SparseArray<VibrationEffect> getEffects() {
+ return mEffects;
+ }
+
+ @Override
+ public long getDuration() {
+ long maxDuration = Long.MIN_VALUE;
+ boolean hasUnknownStep = false;
+ for (int i = 0; i < mEffects.size(); i++) {
+ long duration = mEffects.valueAt(i).getDuration();
+ if (duration == Long.MAX_VALUE) {
+ // If any duration is repeating, this combination duration is also repeating.
+ return duration;
+ }
+ maxDuration = Math.max(maxDuration, duration);
+ // If any step is unknown, this combination duration will also be unknown, unless
+ // any step is repeating. Repeating vibrations take precedence over non-repeating
+ // ones in the service, so continue looping to check for repeating steps.
+ hasUnknownStep |= duration < 0;
+ }
+ if (hasUnknownStep) {
+ // If any step is unknown, this combination duration is also unknown.
+ return -1;
+ }
+ return maxDuration;
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ for (int i = 0; i < mEffects.size(); i++) {
+ if (!mEffects.valueAt(i).isHapticFeedbackCandidate()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ Preconditions.checkArgument(mEffects.size() > 0,
+ "There should be at least one effect set for a combined effect");
+ for (int i = 0; i < mEffects.size(); i++) {
+ mEffects.valueAt(i).validate();
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasVibrator(int vibratorId) {
+ return mEffects.indexOfKey(vibratorId) >= 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Stereo)) {
+ return false;
+ }
+ Stereo other = (Stereo) o;
+ if (mEffects.size() != other.mEffects.size()) {
+ return false;
+ }
+ for (int i = 0; i < mEffects.size(); i++) {
+ if (!mEffects.valueAt(i).equals(other.mEffects.get(mEffects.keyAt(i)))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mEffects.contentHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Stereo{mEffects=" + mEffects + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_STEREO);
+ out.writeInt(mEffects.size());
+ for (int i = 0; i < mEffects.size(); i++) {
+ out.writeInt(mEffects.keyAt(i));
+ mEffects.valueAt(i).writeToParcel(out, flags);
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<Stereo> CREATOR =
+ new Parcelable.Creator<Stereo>() {
+ @Override
+ public Stereo createFromParcel(@NonNull Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Stereo(in);
+ }
+
+ @Override
+ @NonNull
+ public Stereo[] newArray(int size) {
+ return new Stereo[size];
+ }
+ };
+ }
+
+ /**
+ * Represents a list of {@link CombinedVibration CombinedVibrations} that should be played in
+ * sequence.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Sequential extends CombinedVibration {
+ // If a vibration is playing more than 3 effects, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE = 3;
+
+ private final List<CombinedVibration> mEffects;
+ private final List<Integer> mDelays;
+
+ Sequential(Parcel in) {
+ int size = in.readInt();
+ mEffects = new ArrayList<>(size);
+ mDelays = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mDelays.add(in.readInt());
+ mEffects.add(CombinedVibration.CREATOR.createFromParcel(in));
+ }
+ }
+
+ Sequential(@NonNull List<CombinedVibration> effects,
+ @NonNull List<Integer> delays) {
+ mEffects = new ArrayList<>(effects);
+ mDelays = new ArrayList<>(delays);
+ }
+
+ /** Effects to be performed in sequence. */
+ @NonNull
+ public List<CombinedVibration> getEffects() {
+ return mEffects;
+ }
+
+ /** Delay to be applied before each effect in {@link #getEffects()}. */
+ @NonNull
+ public List<Integer> getDelays() {
+ return mDelays;
+ }
+
+ @Override
+ public long getDuration() {
+ boolean hasUnknownStep = false;
+ long durations = 0;
+ final int effectCount = mEffects.size();
+ for (int i = 0; i < effectCount; i++) {
+ CombinedVibration effect = mEffects.get(i);
+ long duration = effect.getDuration();
+ if (duration == Long.MAX_VALUE) {
+ // If any duration is repeating, this combination duration is also repeating.
+ return duration;
+ }
+ durations += duration;
+ // If any step is unknown, this combination duration will also be unknown, unless
+ // any step is repeating. Repeating vibrations take precedence over non-repeating
+ // ones in the service, so continue looping to check for repeating steps.
+ hasUnknownStep |= duration < 0;
+ }
+ if (hasUnknownStep) {
+ // If any step is unknown, this combination duration is also unknown.
+ return -1;
+ }
+ long delays = 0;
+ for (int i = 0; i < effectCount; i++) {
+ delays += mDelays.get(i);
+ }
+ return durations + delays;
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ final int effectCount = mEffects.size();
+ if (effectCount > MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE) {
+ return false;
+ }
+ for (int i = 0; i < effectCount; i++) {
+ if (!mEffects.get(i).isHapticFeedbackCandidate()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ Preconditions.checkArgument(mEffects.size() > 0,
+ "There should be at least one effect set for a combined effect");
+ Preconditions.checkArgument(mEffects.size() == mDelays.size(),
+ "Effect and delays should have equal length");
+ final int effectCount = mEffects.size();
+ for (int i = 0; i < effectCount; i++) {
+ if (mDelays.get(i) < 0) {
+ throw new IllegalArgumentException("Delays must all be >= 0"
+ + " (delays=" + mDelays + ")");
+ }
+ }
+ for (int i = 0; i < effectCount; i++) {
+ CombinedVibration effect = mEffects.get(i);
+ if (effect instanceof Sequential) {
+ throw new IllegalArgumentException(
+ "There should be no nested sequential effects in a combined effect");
+ }
+ effect.validate();
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasVibrator(int vibratorId) {
+ final int effectCount = mEffects.size();
+ for (int i = 0; i < effectCount; i++) {
+ if (mEffects.get(i).hasVibrator(vibratorId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Sequential)) {
+ return false;
+ }
+ Sequential other = (Sequential) o;
+ return mDelays.equals(other.mDelays) && mEffects.equals(other.mEffects);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEffects, mDelays);
+ }
+
+ @Override
+ public String toString() {
+ return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
+ out.writeInt(mEffects.size());
+ for (int i = 0; i < mEffects.size(); i++) {
+ out.writeInt(mDelays.get(i));
+ mEffects.get(i).writeToParcel(out, flags);
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<Sequential> CREATOR =
+ new Parcelable.Creator<Sequential>() {
+ @Override
+ public Sequential createFromParcel(@NonNull Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Sequential(in);
+ }
+
+ @Override
+ @NonNull
+ public Sequential[] newArray(int size) {
+ return new Sequential[size];
+ }
+ };
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<CombinedVibration> CREATOR =
+ new Parcelable.Creator<CombinedVibration>() {
+ @Override
+ public CombinedVibration createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_MONO) {
+ return new Mono(in);
+ } else if (token == PARCEL_TOKEN_STEREO) {
+ return new Stereo(in);
+ } else if (token == PARCEL_TOKEN_SEQUENTIAL) {
+ return new Sequential(in);
+ } else {
+ throw new IllegalStateException(
+ "Unexpected combined vibration event type token in parcel.");
+ }
+ }
+
+ @Override
+ public CombinedVibration[] newArray(int size) {
+ return new CombinedVibration[size];
+ }
+ };
+}
diff --git a/android-34/android/os/ConditionVariable.java b/android-34/android/os/ConditionVariable.java
new file mode 100644
index 0000000..a13eaa6
--- /dev/null
+++ b/android-34/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-34/android/os/ConfigUpdate.java b/android-34/android/os/ConfigUpdate.java
new file mode 100644
index 0000000..4908919
--- /dev/null
+++ b/android-34/android/os/ConfigUpdate.java
@@ -0,0 +1,152 @@
+/*
+ * 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.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+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";
+
+ /**
+ * Update the emergency number database into the devices.
+ * <p>Extra: {@link #EXTRA_VERSION} the numeric version of the database.
+ * <p>Extra: {@link #EXTRA_REQUIRED_HASH} hash of the database, which is encoded by base-16
+ * SHA512.
+ * <p>Input: {@link android.content.Intent#getData} the URI to download emergency number
+ * database.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_UPDATE_EMERGENCY_NUMBER_DB =
+ "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
+
+ /**
+ * An integer to indicate the numeric version of the new data. Devices should only install
+ * if the update version is newer than the current one.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_VERSION = "android.os.extra.VERSION";
+
+ /**
+ * Hash of the database, which is encoded by base-16 SHA512.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
+
+ private ConfigUpdate() {
+ }
+}
diff --git a/android-34/android/os/CoolingDevice.java b/android-34/android/os/CoolingDevice.java
new file mode 100644
index 0000000..4ddcd9d
--- /dev/null
+++ b/android-34/android/os/CoolingDevice.java
@@ -0,0 +1,180 @@
+/*
+ * 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.annotation.Nullable;
+import android.hardware.thermal.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,
+ TYPE_TPU,
+ TYPE_POWER_AMPLIFIER,
+ TYPE_DISPLAY,
+ TYPE_SPEAKER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /** Keep in sync with hardware/interfaces/thermal/aidl/android/hardware/thermal
+ * /ThrottlingSeverity.aidl */
+ /** 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 cooling deivice */
+ public static final int TYPE_NPU = CoolingType.NPU;
+ /** Generic passive cooling deivice */
+ public static final int TYPE_COMPONENT = CoolingType.COMPONENT;
+ /** TPU cooling deivice */
+ public static final int TYPE_TPU = CoolingType.TPU;
+ /** Power amplifier cooling device */
+ public static final int TYPE_POWER_AMPLIFIER = CoolingType.POWER_AMPLIFIER;
+ /** Display cooling device */
+ public static final int TYPE_DISPLAY = CoolingType.DISPLAY;
+ /** Speaker cooling device */
+ public static final int TYPE_SPEAKER = CoolingType.SPEAKER;
+
+ /**
+ * 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_SPEAKER;
+ }
+
+ 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(@Nullable 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-34/android/os/CountDownTimer.java b/android-34/android/os/CountDownTimer.java
new file mode 100644
index 0000000..51faa85
--- /dev/null
+++ b/android-34/android/os/CountDownTimer.java
@@ -0,0 +1,171 @@
+/*
+ * 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:
+ *
+ * <div>
+ * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
+ * <pre class="prettyprint lang-kotlin">
+ * object : CountDownTimer(30000, 1000) {
+ *
+ * override fun onTick(millisUntilFinished: Long) {
+ * mTextField.setText("seconds remaining: " + millisUntilFinished / 1000)
+ * }
+ *
+ * override fun onFinish() {
+ * mTextField.setText("done!")
+ * }
+ * }.start()
+ * </pre>
+ * </section><section><h3 id="java">Java</h3>
+ * <pre class="prettyprint lang-java">
+ * new CountDownTimer(30000, 1000) {
+ *
+ * public void onTick(long millisUntilFinished) {
+ * mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
+ * }
+ *
+ * public void onFinish() {
+ * mTextField.setText("done!");
+ * }
+ * }.start();
+ * </pre></section></div></div>
+ *
+ * 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-34/android/os/CpuUsageInfo.java b/android-34/android/os/CpuUsageInfo.java
new file mode 100644
index 0000000..444579f
--- /dev/null
+++ b/android-34/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-34/android/os/CpuUsageTrackingPerfTest.java b/android-34/android/os/CpuUsageTrackingPerfTest.java
new file mode 100644
index 0000000..0d7b7ca
--- /dev/null
+++ b/android-34/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-34/android/os/DeadObjectException.java b/android-34/android/os/DeadObjectException.java
new file mode 100644
index 0000000..e06b0f9
--- /dev/null
+++ b/android-34/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-34/android/os/DeadSystemException.java b/android-34/android/os/DeadSystemException.java
new file mode 100644
index 0000000..8fb53e2
--- /dev/null
+++ b/android-34/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-34/android/os/DeadSystemRuntimeException.java b/android-34/android/os/DeadSystemRuntimeException.java
new file mode 100644
index 0000000..1e86924
--- /dev/null
+++ b/android-34/android/os/DeadSystemRuntimeException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Exception thrown when a call into system_server resulted in a
+ * DeadObjectException, meaning that the system_server has died. There's
+ * nothing apps can do at this point - the system will automatically restart -
+ * so there's no point in catching this.
+ *
+ * @hide
+ */
+public class DeadSystemRuntimeException extends RuntimeException {
+ public DeadSystemRuntimeException() {
+ super(new DeadSystemException());
+ }
+}
diff --git a/android-34/android/os/Debug.java b/android-34/android/os/Debug.java
new file mode 100644
index 0000000..62d9c69
--- /dev/null
+++ b/android-34/android/os/Debug.java
@@ -0,0 +1,2687 @@
+/*
+ * 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.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
+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.
+ */
+ // This must match VMDebug.TRACE_COUNT_ALLOCS.
+ @Deprecated
+ public static final int TRACE_COUNT_ALLOCS = 1;
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public int otherSwappedOutPss;
+
+ /** Whether the kernel reports proportional swap usage */
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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_ZYGOTE_CODE_CACHE = 23;
+ /** @hide */
+ public static final int OTHER_DALVIK_OTHER_APP_CODE_CACHE = 24;
+ /** @hide */
+ public static final int OTHER_DALVIK_OTHER_COMPILER_METADATA = 25;
+ /** @hide */
+ public static final int OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE = 26;
+ /** @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 = 27;
+ /** @hide */
+ public static final int OTHER_DEX_APP_DEX = 28;
+ /** @hide */
+ public static final int OTHER_DEX_APP_VDEX = 29;
+ /** @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 = 30;
+ /** @hide */
+ public static final int OTHER_ART_BOOT = 31;
+ /** @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 = OTHER_ART_BOOT + 1 - OTHER_DALVIK_NORMAL;
+
+ /** @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_ZYGOTE_CODE_CACHE: return ".ZygoteJIT";
+ case OTHER_DALVIK_OTHER_APP_CODE_CACHE: return ".AppJIT";
+ 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 PSS 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)
+ + getOtherPrivate(OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE)
+ + getOtherPrivate(OTHER_DALVIK_OTHER_APP_CODE_CACHE);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Rss of Java Heap bytes in KB due to the application.
+ * @hide
+ */
+ public int getSummaryJavaHeapRss() {
+ return dalvikRss + getOtherRss(OTHER_ART);
+ }
+
+ /**
+ * Rss of Native Heap bytes in KB due to the application.
+ * @hide
+ */
+ public int getSummaryNativeHeapRss() {
+ return nativeRss;
+ }
+
+ /**
+ * Rss of code and other static resource bytes in KB due to
+ * the application.
+ * @hide
+ */
+ public int getSummaryCodeRss() {
+ return getOtherRss(OTHER_SO)
+ + getOtherRss(OTHER_JAR)
+ + getOtherRss(OTHER_APK)
+ + getOtherRss(OTHER_TTF)
+ + getOtherRss(OTHER_DEX)
+ + getOtherRss(OTHER_OAT)
+ + getOtherRss(OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE)
+ + getOtherRss(OTHER_DALVIK_OTHER_APP_CODE_CACHE);
+ }
+
+ /**
+ * Rss in KB of the stack due to the application.
+ * @hide
+ */
+ public int getSummaryStackRss() {
+ return getOtherRss(OTHER_STACK);
+ }
+
+ /**
+ * Rss in KB of graphics due to the application.
+ * @hide
+ */
+ public int getSummaryGraphicsRss() {
+ return getOtherRss(OTHER_GL_DEV)
+ + getOtherRss(OTHER_GRAPHICS)
+ + getOtherRss(OTHER_GL);
+ }
+
+ /**
+ * Rss in KB due to either the application or system that haven't otherwise been
+ * accounted for.
+ * @hide
+ */
+ public int getSummaryUnknownRss() {
+ return getTotalRss()
+ - getSummaryJavaHeapRss()
+ - getSummaryNativeHeapRss()
+ - getSummaryCodeRss()
+ - getSummaryStackRss()
+ - getSummaryGraphicsRss();
+ }
+
+ /**
+ * 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 a debugger attaches,
+ * suspend all Java threads and send VM_START (a.k.a VM_INIT)
+ * packet.
+ *
+ * @hide
+ */
+ public static void suspendAllAndSendVmStart() {
+ if (!VMDebug.isDebuggingEnabled()) {
+ 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);
+
+ // We must wait until a debugger is connected (debug socket is
+ // open and at least one non-DDM JDWP packedt has been received.
+ // This guarantees that oj-libjdwp has been attached and that
+ // ART's default implementation of suspendAllAndSendVmStart has
+ // been replaced with an implementation that will suspendAll and
+ // send VM_START.
+ System.out.println("Waiting for debugger first packet");
+
+ mWaiting = true;
+ while (!isDebuggerConnected()) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ }
+ }
+ mWaiting = false;
+
+ System.out.println("Debug.suspendAllAndSentVmStart");
+ VMDebug.suspendAllAndSendVmStart();
+ System.out.println("Debug.suspendAllAndSendVmStart, resumed");
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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() {
+ // 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()}.
+ *
+ * @deprecated Please use other tracing method in this class.
+ */
+ public static void enableEmulatorTraceOutput() {
+ Log.w(TAG, "Unimplemented");
+ }
+
+ /**
+ * 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.
+ * All statistics are approximate. Individual allocations may not be immediately reflected
+ * in the results.
+ * 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.
+ *
+ * @return true if the meminfo was read successfully, false if not (i.e., given pid has gone).
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static native boolean 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 sizes (up to 4 values in order): the
+ * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other.
+ *
+ * @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone).
+ * @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;
+ /**
+ * Note: MEMINFO_KRECLAIMABLE includes MEMINFO_SLAB_RECLAIMABLE (see KReclaimable field
+ * description in kernel documentation).
+ * @hide
+ */
+ public static final int MEMINFO_KRECLAIMABLE = 15;
+ /** @hide */
+ public static final int MEMINFO_ACTIVE = 16;
+ /** @hide */
+ public static final int MEMINFO_INACTIVE = 17;
+ /** @hide */
+ public static final int MEMINFO_UNEVICTABLE = 18;
+ /** @hide */
+ public static final int MEMINFO_AVAILABLE = 19;
+ /** @hide */
+ public static final int MEMINFO_ACTIVE_ANON = 20;
+ /** @hide */
+ public static final int MEMINFO_INACTIVE_ANON = 21;
+ /** @hide */
+ public static final int MEMINFO_ACTIVE_FILE = 22;
+ /** @hide */
+ public static final int MEMINFO_INACTIVE_FILE = 23;
+ /** @hide */
+ public static final int MEMINFO_CMA_TOTAL = 24;
+ /** @hide */
+ public static final int MEMINFO_CMA_FREE = 25;
+ /** @hide */
+ public static final int MEMINFO_COUNT = 26;
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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();
+
+ /**
+ * 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();
+ StringBuilder sb = new StringBuilder();
+ 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();
+ StringBuilder sb = new StringBuilder();
+ 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();
+ StringBuilder sb = new StringBuilder();
+ 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();
+
+ /**
+ * Return total memory size in kilobytes for exported DMA-BUFs or -1 if
+ * the DMA-BUF sysfs stats at /sys/kernel/dmabuf/buffers could not be read.
+ *
+ * @hide
+ */
+ public static native long getDmabufTotalExportedKb();
+
+ /**
+ * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF
+ * heaps frameworks or -1 in the case of an error.
+ *
+ * @hide
+ */
+ public static native long getDmabufHeapTotalExportedKb();
+
+ /**
+ * Return memory size in kilobytes allocated for ION heaps or -1 if
+ * /sys/kernel/ion/total_heaps_kb could not be read.
+ *
+ * @hide
+ */
+ public static native long getIonHeapsSizeKb();
+
+ /**
+ * Return memory size in kilobytes allocated for DMA-BUF heap pools or -1 if
+ * /sys/kernel/dma_heap/total_pools_kb could not be read.
+ *
+ * @hide
+ */
+ public static native long getDmabufHeapPoolsSizeKb();
+
+ /**
+ * Return memory size in kilobytes allocated for ION pools or -1 if
+ * /sys/kernel/ion/total_pools_kb could not be read.
+ *
+ * @hide
+ */
+ public static native long getIonPoolsSizeKb();
+
+ /**
+ * Returns the global total GPU-private memory in kB or -1 on error.
+ *
+ * @hide
+ */
+ public static native long getGpuPrivateMemoryKb();
+
+ /**
+ * Return DMA-BUF memory mapped by processes in kB.
+ * Notes:
+ * * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
+ *
+ * @hide
+ */
+ public static native long getDmabufMappedSizeKb();
+
+ /**
+ * Return memory size in kilobytes used by GPU.
+ *
+ * @hide
+ */
+ public static native long getGpuTotalUsageKb();
+
+ /**
+ * Return whether virtually-mapped kernel stacks are enabled (CONFIG_VMAP_STACK).
+ * Note: caller needs config_gz read sepolicy permission
+ *
+ * @hide
+ */
+ public static native boolean isVmapStack();
+}
diff --git a/android-34/android/os/DeviceIdleManager.java b/android-34/android/os/DeviceIdleManager.java
new file mode 100644
index 0000000..6cdf585
--- /dev/null
+++ b/android-34/android/os/DeviceIdleManager.java
@@ -0,0 +1,93 @@
+/*
+ * 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.RequiresPermission;
+import android.annotation.SystemApi;
+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
+ */
+@SystemApi
+@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;
+ }
+
+ IDeviceIdleController getService() {
+ return mService;
+ }
+
+ /**
+ * Ends any active idle session.
+ *
+ * @param reason The reason to end. Used for debugging purposes.
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void endIdle(@NonNull String reason) {
+ try {
+ mService.exitIdle(reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return package names the system has white-listed to opt out of power save restrictions,
+ * except for device idle mode.
+ *
+ * @hide Should be migrated to PowerExemptionManager
+ */
+ @TestApi
+ public @NonNull String[] getSystemPowerWhitelistExceptIdle() {
+ try {
+ return mService.getSystemPowerWhitelistExceptIdle();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return package names the system has white-listed to opt out of power save restrictions for
+ * all modes.
+ *
+ * @hide Should be migrated to PowerExemptionManager
+ */
+ @TestApi
+ public @NonNull String[] getSystemPowerWhitelist() {
+ try {
+ return mService.getSystemPowerWhitelist();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android-34/android/os/DisplayPerfTest.java b/android-34/android/os/DisplayPerfTest.java
new file mode 100644
index 0000000..0cce6ad
--- /dev/null
+++ b/android-34/android/os/DisplayPerfTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.hardware.display.DisplayManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.provider.Settings;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPerfTest {
+ private static final float DELTA = 0.001f;
+
+ @Rule
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private DisplayManager mDisplayManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ }
+
+ @Test
+ public void testBrightnessChanges() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ SystemClock.sleep(20);
+ float brightness = 0.3f;
+ while (state.keepRunning()) {
+ setAndWaitToChangeBrightness(brightness);
+ brightness = toggleBrightness(brightness);
+ }
+ }
+
+ private float toggleBrightness(float oldBrightness) {
+ float[] brightnesses = new float[]{0.3f, 0.5f};
+ if (oldBrightness == brightnesses[0]) {
+ return brightnesses[1];
+ }
+ return brightnesses[0];
+ }
+
+ private void setAndWaitToChangeBrightness(float brightness) throws Exception {
+ mDisplayManager.setBrightness(0, brightness);
+ PollingCheck.check("Brightness is not set to the expected value", 500,
+ () -> isInRange(mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY), brightness,
+ DELTA));
+ }
+
+ private boolean isInRange(float value, float target, float delta) {
+ return target - delta <= value && target + delta >= value;
+ }
+}
diff --git a/android-34/android/os/DropBoxManager.java b/android-34/android/os/DropBoxManager.java
new file mode 100644
index 0000000..403f55c
--- /dev/null
+++ b/android-34/android/os/DropBoxManager.java
@@ -0,0 +1,410 @@
+/*
+ * 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.BytesLong;
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxManagerService;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+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;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "IS_" }, value = { IS_EMPTY, IS_TEXT, IS_GZIPPED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /** 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 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 @NonNull String mTag;
+ private final @CurrentTimeMillisLong long mTimeMillis;
+
+ private final @Nullable byte[] mData;
+ private final @Nullable ParcelFileDescriptor mFileDescriptor;
+ private final @Flags int mFlags;
+
+ /** Create a new empty Entry with no contents. */
+ public Entry(@NonNull String tag, @CurrentTimeMillisLong 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(@NonNull String tag, @CurrentTimeMillisLong long millis,
+ @NonNull 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(StandardCharsets.UTF_8);
+ 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(@NonNull String tag, @CurrentTimeMillisLong long millis,
+ @Nullable byte[] data, @Flags 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(@NonNull String tag, @CurrentTimeMillisLong long millis,
+ @Nullable ParcelFileDescriptor data, @Flags 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(@NonNull String tag, @CurrentTimeMillisLong long millis,
+ @NonNull File data, @Flags 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 @NonNull String getTag() {
+ return mTag;
+ }
+
+ /** @return time when the entry was originally created. */
+ public @CurrentTimeMillisLong long getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ /** @return flags describing the content returned by {@link #getInputStream()}. */
+ public @Flags int getFlags() {
+ // getInputStream() decompresses.
+ return mFlags & ~IS_GZIPPED;
+ }
+
+ /**
+ * @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 @Nullable String getText(@BytesLong 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 @Nullable 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(new BufferedInputStream(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 an 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(@NonNull String tag, @NonNull String data) {
+ addData(tag, data.getBytes(StandardCharsets.UTF_8), IS_TEXT);
+ }
+
+ /**
+ * 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(@NonNull String tag, @Nullable byte[] data, @Flags int flags) {
+ if (data == null) throw new NullPointerException("data == null");
+ try {
+ mService.addData(tag, 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(@NonNull String tag, @NonNull File file, @Flags int flags)
+ throws IOException {
+ if (file == null) throw new NullPointerException("file == null");
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_READ_ONLY)) {
+ mService.addFile(tag, pfd, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } 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-34/android/os/Environment.java b/android-34/android/os/Environment.java
new file mode 100644
index 0000000..536ef31
--- /dev/null
+++ b/android-34/android/os/Environment.java
@@ -0,0 +1,1597 @@
+/*
+ * 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.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+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.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * 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_SYSTEM_EXT_ROOT = "SYSTEM_EXT_ROOT";
+ private static final String ENV_APEX_ROOT = "APEX_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";
+
+ /**
+ * The folder name prefix for the user credential protected data directory. This is exposed for
+ * use in string path caching for {@link ApplicationInfo} objects, and should not be accessed
+ * directly otherwise. Prefer {@link #getDataUserCeDirectory(String, int)}.
+ * {@hide}
+ */
+ public static final String DIR_USER_CE = "user";
+
+ /**
+ * The folder name prefix for the user device protected data directory. This is exposed for use
+ * in string path caching for {@link ApplicationInfo} objects, and should not be accessed
+ * directly otherwise. Prefer {@link #getDataUserDeDirectory(String, int)}.
+ * {@hide}
+ */
+ public static final String DIR_USER_DE = "user_de";
+
+ /** {@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 String DIR_ANDROID_DATA_PATH = getDirectoryPath(ENV_ANDROID_DATA, "/data");
+ private static final File DIR_ANDROID_DATA = new File(DIR_ANDROID_DATA_PATH);
+ 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_METADATA = new File("/metadata");
+ 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_SYSTEM_EXT_ROOT = getDirectory(ENV_SYSTEM_EXT_ROOT,
+ "/system_ext");
+ private static final File DIR_APEX_ROOT = getDirectory(ENV_APEX_ROOT,
+ "/apex");
+
+ /**
+ * Scoped Storage is on by default. However, it is not strictly enforced and there are multiple
+ * ways to opt out of scoped storage:
+ * <ul>
+ * <li>Target Sdk < Q</li>
+ * <li>Target Sdk = Q and has `requestLegacyExternalStorage` set in AndroidManifest.xml</li>
+ * <li>Target Sdk > Q: Upgrading from an app that was opted out of scoped storage and has
+ * `preserveLegacyExternalStorage` set in AndroidManifest.xml</li>
+ * </ul>
+ * This flag is enabled for all apps by default as Scoped Storage is enabled by default.
+ * Developers can disable this flag to opt out of Scoped Storage and have legacy storage
+ * workflow.
+ *
+ * Note: {@code FORCE_ENABLE_SCOPED_STORAGE} should also be disabled for apps to opt out of
+ * scoped storage.
+ * Note: This flag is also used in {@code com.android.providers.media.LocalCallingIdentity}.
+ * Any modifications to this flag should be reflected there as well.
+ * See https://developer.android.com/training/data-storage#scoped-storage for more information.
+ */
+ @ChangeId
+ private static final long DEFAULT_SCOPED_STORAGE = 149924527L;
+
+ /**
+ * See definition in com.android.providers.media.LocalCallingIdentity
+ */
+ /**
+ * Setting this flag strictly enforces Scoped Storage regardless of:
+ * <ul>
+ * <li>The value of Target Sdk</li>
+ * <li>The value of `requestLegacyExternalStorage` in AndroidManifest.xml</li>
+ * <li>The value of `preserveLegacyExternalStorage` in AndroidManifest.xml</li>
+ * </ul>
+ *
+ * Note: {@code DEFAULT_SCOPED_STORAGE} should also be enabled for apps to be enforced into
+ * scoped storage.
+ * Note: This flag is also used in {@code com.android.providers.media.LocalCallingIdentity}.
+ * Any modifications to this flag should be reflected there as well.
+ * See https://developer.android.com/training/data-storage#scoped-storage for more information.
+ */
+ @ChangeId
+ @Disabled
+ private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L;
+
+ @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
+ public File getExternalStorageDirectory() {
+ return getExternalDirs()[0];
+ }
+
+ @UnsupportedAppUsage
+ 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;
+ }
+
+ /**
+ * Return root directory where all external storage devices will be mounted.
+ * For example, {@link #getExternalStorageDirectory()} will appear under
+ * this location.
+ */
+ 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
+ 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.
+ *
+ * @deprecated This directory is not guaranteed to exist.
+ * Its name is changed to "system_ext" because the partition's purpose is changed.
+ * {@link #getSystemExtDirectory()}
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ public static @NonNull File getProductServicesDirectory() {
+ return getDirectory("PRODUCT_SERVICES_ROOT", "/product_services");
+ }
+
+ /**
+ * Return root directory of the "system_ext" partition holding system partition's extension
+ * If present, the partition is mounted read-only.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull File getSystemExtDirectory() {
+ return DIR_SYSTEM_EXT_ROOT;
+ }
+
+ /**
+ * Return root directory of the apex mount point, where all the apex modules are made available
+ * to the rest of the system.
+ *
+ * @hide
+ */
+ public static @NonNull File getApexDirectory() {
+ return DIR_APEX_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;
+ }
+
+ /**
+ * @see #getDataDirectory()
+ * @hide
+ */
+ public static String getDataDirectoryPath() {
+ return DIR_ANDROID_DATA_PATH;
+ }
+
+ /** {@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 String getDataDirectoryPath(String volumeUuid) {
+ if (TextUtils.isEmpty(volumeUuid)) {
+ return DIR_ANDROID_DATA_PATH;
+ } else {
+ return getExpandDirectory().getAbsolutePath() + File.separator + 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} */
+ private static File getDataMiscCeDirectory(String volumeUuid, int userId) {
+ return buildPath(getDataDirectory(volumeUuid), "misc_ce", String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getDataMiscCeSharedSdkSandboxDirectory(String volumeUuid, int userId,
+ String packageName) {
+ return buildPath(getDataMiscCeDirectory(volumeUuid, userId), "sdksandbox",
+ packageName, "shared");
+ }
+
+ /** {@hide} */
+ public static File getDataMiscDeDirectory(int userId) {
+ return buildPath(getDataDirectory(), "misc_de", String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ private static File getDataMiscDeDirectory(String volumeUuid, int userId) {
+ return buildPath(getDataDirectory(volumeUuid), "misc_de", String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getDataMiscDeSharedSdkSandboxDirectory(String volumeUuid, int userId,
+ String packageName) {
+ return buildPath(getDataMiscDeDirectory(volumeUuid, userId), "sdksandbox",
+ packageName, "shared");
+ }
+
+ 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), DIR_USER_CE);
+ }
+
+ /** {@hide} */
+ public static File getDataUserCeDirectory(String volumeUuid, int userId) {
+ return new File(getDataUserCeDirectory(volumeUuid), String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ @NonNull
+ public static File getDataUserCePackageDirectory(@Nullable String volumeUuid, int userId,
+ @NonNull String packageName) {
+ // TODO: keep consistent with installd
+ return new File(getDataUserCeDirectory(volumeUuid, userId), packageName);
+ }
+
+ /**
+ * Retrieve the credential encrypted data directory for a specific package of a specific user.
+ * This is equivalent to {@link ApplicationInfo#credentialProtectedDataDir}, exposed because
+ * fetching a full {@link ApplicationInfo} instance may be expensive if all the caller needs
+ * is this directory.
+ *
+ * @param storageUuid The storage volume for this directory, usually retrieved from a
+ * {@link StorageManager} API or {@link ApplicationInfo#storageUuid}.
+ * @param user The user this directory is for.
+ * @param packageName The app this directory is for.
+ *
+ * @see ApplicationInfo#credentialProtectedDataDir
+ * @return A file to the directory.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public static File getDataCePackageDirectoryForUser(@NonNull UUID storageUuid,
+ @NonNull UserHandle user, @NonNull String packageName) {
+ var volumeUuid = StorageManager.convert(storageUuid);
+ return getDataUserCePackageDirectory(volumeUuid, user.getIdentifier(), packageName);
+ }
+
+ /** {@hide} */
+ public static File getDataUserDeDirectory(String volumeUuid) {
+ return new File(getDataDirectory(volumeUuid), DIR_USER_DE);
+ }
+
+ /** {@hide} */
+ public static File getDataUserDeDirectory(String volumeUuid, int userId) {
+ return new File(getDataUserDeDirectory(volumeUuid), String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ @NonNull
+ public static File getDataUserDePackageDirectory(@Nullable String volumeUuid, int userId,
+ @NonNull String packageName) {
+ // TODO: keep consistent with installd
+ return new File(getDataUserDeDirectory(volumeUuid, userId), packageName);
+ }
+
+ /**
+ * Retrieve the device encrypted data directory for a specific package of a specific user. This
+ * is equivalent to {@link ApplicationInfo#deviceProtectedDataDir}, exposed because fetching a
+ * full {@link ApplicationInfo} instance may be expensive if all the caller needs is this
+ * directory.
+ *
+ * @param storageUuid The storage volume for this directory, usually retrieved from a
+ * {@link StorageManager} API or {@link ApplicationInfo#storageUuid}.
+ * @param user The user this directory is for.
+ * @param packageName The app this directory is for.
+ *
+ * @see ApplicationInfo#deviceProtectedDataDir
+ * @return A file to the directory.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public static File getDataDePackageDirectoryForUser(@NonNull UUID storageUuid,
+ @NonNull UserHandle user, @NonNull String packageName) {
+ var volumeUuid = StorageManager.convert(storageUuid);
+ return getDataUserDePackageDirectory(volumeUuid, user.getIdentifier(), 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 locations where media files (such as ringtones, notification
+ * sounds, or alarm sounds) may be located on internal storage. These are
+ * typically indexed under {@link MediaStore#VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull Collection<File> getInternalMediaDirectories() {
+ final ArrayList<File> res = new ArrayList<>();
+ addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
+ addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
+ addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
+ return res;
+ }
+
+ private static void addCanonicalFile(List<File> list, File file) {
+ try {
+ list.add(file.getCanonicalFile());
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve " + file + ": " + e);
+ list.add(file);
+ }
+ }
+
+ /**
+ * 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}
+ * <p>
+ * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or
+ * {@link MediaStore} offer better performance.
+ *
+ * @see #getExternalStorageState()
+ * @see #isExternalStorageRemovable()
+ */
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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_AUDIOBOOKS},
+ * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
+ * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, and
+ * {@link #DIRECTORY_RECORDINGS} as a series of directories to
+ * categorize 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_AUDIOBOOKS}, {@link #DIRECTORY_NOTIFICATIONS},
+ * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, and
+ * {@link #DIRECTORY_RECORDINGS} as a series of directories to
+ * categorize 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_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_ALARMS},
+ * and {@link #DIRECTORY_RECORDINGS} as a series of directories
+ * to categorize 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_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_RINGTONES},
+ * and {@link #DIRECTORY_RECORDINGS} as a series of directories
+ * to categorize 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_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES}, and
+ * {@link #DIRECTORY_RECORDINGS} as a series of directories to
+ * categorize 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 that should be
+ * in the list of audiobooks that the user can select (not as regular
+ * music).
+ * This may be combined with {@link #DIRECTORY_MUSIC},
+ * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
+ * {@link #DIRECTORY_ALARMS}, {@link #DIRECTORY_RINGTONES},
+ * and {@link #DIRECTORY_RECORDINGS} as a series of directories
+ * to categorize a particular audio file as more than one type.
+ */
+ public static String DIRECTORY_AUDIOBOOKS = "Audiobooks";
+
+ /**
+ * Standard directory in which to place any audio files that should be
+ * in the list of voice recordings recorded by voice recorder apps that
+ * the user can select (not as regular music).
+ * This may be combined with {@link #DIRECTORY_MUSIC},
+ * {@link #DIRECTORY_AUDIOBOOKS}, {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_ALARMS},
+ * and {@link #DIRECTORY_RINGTONES} as a series of directories
+ * to categorize a particular audio file as more than one type.
+ */
+ @NonNull
+ // The better way is that expose a static method getRecordingDirectories.
+ // But since it's an existing API surface and developers already
+ // used to DIRECTORY_* constants, we should keep using this pattern
+ // for consistency. We use SuppressLint here to avoid exposing a final
+ // field. A final field will prevent us from ever changing the value of
+ // DIRECTORY_RECORDINGS. Not that it's likely that we will ever need to
+ // change it, but it's better to have such option.
+ @SuppressLint({"MutableBareField", "AllUpper"})
+ public static String DIRECTORY_RECORDINGS = "Recordings";
+
+ /**
+ * 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}
+ * <li>{@link #DIRECTORY_RECORDINGS}
+ * </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,
+ DIRECTORY_RECORDINGS,
+ };
+
+ /**
+ * @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_RECORDINGS = 1 << 11;
+
+ /** {@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_RECORDINGS.equals(name)) res |= HAS_RECORDINGS;
+ else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID;
+ else res |= HAS_OTHER;
+ }
+ }
+ return res;
+ }
+
+ private static boolean hasInterestingFiles(File dir) {
+ final ArrayDeque<File> explore = new ArrayDeque<>();
+ 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}
+ * <p>
+ * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or
+ * {@link MediaStore} offer better performance.
+ *
+ * @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()}.
+ */
+ 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();
+ }
+
+ /**
+ * Returns the path for android-specific OBB data on the SD card.
+ * @hide
+ */
+ public static File[] buildExternalStorageAndroidObbDirs() {
+ throwIfUserRequired();
+ return sCurrentUser.buildExternalStorageAndroidObbDirs();
+ }
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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;
+ }
+
+ /**
+ * Return the metadata directory.
+ *
+ * @hide
+ */
+ public static @NonNull File getMetadataDirectory() {
+ return DIR_METADATA;
+ }
+
+ /**
+ * 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 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, target sdk and other
+ * factors.
+ * <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 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, target sdk and other
+ * factors.
+ * <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;
+ // Isolated processes and Instant apps are never allowed to be in scoped storage
+ if (Process.isIsolated(uid) || Process.isSdkSandboxUid(uid)) {
+ return false;
+ }
+
+ final PackageManager packageManager = context.getPackageManager();
+ if (packageManager.isInstantApp()) {
+ return false;
+ }
+
+ // Apps with PROPERTY_NO_APP_DATA_STORAGE should not be allowed in scoped storage
+ final String packageName = AppGlobals.getInitialPackage();
+ try {
+ final PackageManager.Property noAppStorageProp = packageManager.getProperty(
+ PackageManager.PROPERTY_NO_APP_DATA_STORAGE, packageName);
+ if (noAppStorageProp != null && noAppStorageProp.getBoolean()) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException ignore) {
+ // Property not defined for the package
+ }
+
+ boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE);
+ boolean forceEnableScopedStorage = Compatibility.isChangeEnabled(
+ FORCE_ENABLE_SCOPED_STORAGE);
+ // if Scoped Storage is strictly enforced, the app does *not* have legacy storage access
+ // Note: does not require packagename/uid as this is directly called from an app process
+ if (isScopedStorageEnforced(defaultScopedStorage, forceEnableScopedStorage)) {
+ return false;
+ }
+ // if Scoped Storage is strictly disabled, the app has legacy storage access
+ // Note: does not require packagename/uid as this is directly called from an app process
+ if (isScopedStorageDisabled(defaultScopedStorage, forceEnableScopedStorage)) {
+ return true;
+ }
+
+ final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+ final String opPackageName = context.getOpPackageName();
+
+ if (appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, uid,
+ opPackageName) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+
+ // Legacy external storage access is granted to instrumentations invoked with
+ // "--no-isolated-storage" flag.
+ return appOps.noteOpNoThrow(AppOpsManager.OP_NO_ISOLATED_STORAGE, uid,
+ opPackageName) == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
+ boolean forceEnableScopedStorage) {
+ return defaultScopedStorage && forceEnableScopedStorage;
+ }
+
+ private static boolean isScopedStorageDisabled(boolean defaultScopedStorage,
+ boolean forceEnableScopedStorage) {
+ return !defaultScopedStorage && !forceEnableScopedStorage;
+ }
+
+ /**
+ * Returns whether the calling app has All Files Access on the primary shared/external storage
+ * media.
+ * <p>Declaring the permission {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} isn't
+ * enough to gain the access.
+ * <p>To request access, use
+ * {@link android.provider.Settings#ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION}.
+ */
+ public static boolean isExternalStorageManager() {
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
+ return isExternalStorageManager(externalDir);
+ }
+
+ /**
+ * Returns whether the calling app has All Files Access at the given {@code path}
+ * <p>Declaring the permission {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} isn't
+ * enough to gain the access.
+ * <p>To request access, use
+ * {@link android.provider.Settings#ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION}.
+ */
+ public static boolean isExternalStorageManager(@NonNull File path) {
+ final Context context = Objects.requireNonNull(AppGlobals.getInitialApplication());
+ String packageName = Objects.requireNonNull(context.getPackageName());
+ int uid = context.getApplicationInfo().uid;
+
+ final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+ final int opMode =
+ appOps.checkOpNoThrow(AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE, uid, packageName);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_DEFAULT:
+ return PackageManager.PERMISSION_GRANTED
+ == context.checkPermission(
+ Manifest.permission.MANAGE_EXTERNAL_STORAGE, Process.myPid(), uid);
+ case AppOpsManager.MODE_ALLOWED:
+ return true;
+ case AppOpsManager.MODE_ERRORED:
+ case AppOpsManager.MODE_IGNORED:
+ return false;
+ default:
+ throw new IllegalStateException("Unknown AppOpsManager mode " + opMode);
+ }
+ }
+
+ static File getDirectory(String variableName, String defaultPath) {
+ String path = System.getenv(variableName);
+ return path == null ? new File(defaultPath) : new File(path);
+ }
+
+ @NonNull
+ static String getDirectoryPath(@NonNull String variableName, @NonNull String defaultPath) {
+ String path = System.getenv(variableName);
+ return path == null ? defaultPath : 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-34/android/os/ExternalVibration.java b/android-34/android/os/ExternalVibration.java
new file mode 100644
index 0000000..fb115b3
--- /dev/null
+++ b/android-34/android/os/ExternalVibration.java
@@ -0,0 +1,216 @@
+/*
+ * 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.Nullable;
+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) {
+ this(uid, pkg, attrs, controller, new Binder());
+ }
+
+ /**
+ * Full constructor, but exposed to construct the ExternalVibration with an explicit binder
+ * token (for mocks).
+ *
+ * @hide
+ */
+ public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs,
+ @NonNull IExternalVibrationController controller, @NonNull IBinder token) {
+ mUid = uid;
+ mPkg = Preconditions.checkNotNull(pkg);
+ mAttrs = Preconditions.checkNotNull(attrs);
+ mController = Preconditions.checkNotNull(controller);
+ mToken = Preconditions.checkNotNull(token);
+
+ // IExternalVibrationController is a hidden AIDL interface with implementation provided by
+ // the audio framework to allow mute/unmute control over the external vibration.
+ //
+ // Transactions are locked in audioflinger, and should be blocking to avoid racing
+ // conditions on multiple audio playback.
+ //
+ // They can also be triggered before starting a new external vibration in
+ // IExternalVibratorService, as the ongoing external vibration needs to be muted before the
+ // new one can start, which also requires blocking calls to mute.
+ Binder.allowBlocking(mController.asBinder());
+ }
+
+ private ExternalVibration(Parcel in) {
+ this(in.readInt(), in.readString(), readAudioAttributes(in),
+ IExternalVibrationController.Stub.asInterface(in.readStrongBinder()),
+ in.readStrongBinder());
+ }
+
+ private static 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;
+ }
+
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ public VibrationAttributes getVibrationAttributes() {
+ return new VibrationAttributes.Builder(mAttrs).build();
+ }
+
+ /**
+ * 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(@Nullable 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=" + mToken
+ + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mUid);
+ out.writeString(mPkg);
+ writeAudioAttributes(mAttrs, out, 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-34/android/os/FactoryTest.java b/android-34/android/os/FactoryTest.java
new file mode 100644
index 0000000..b59227c
--- /dev/null
+++ b/android-34/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-34/android/os/FileBridge.java b/android-34/android/os/FileBridge.java
new file mode 100644
index 0000000..9dcdbf9
--- /dev/null
+++ b/android-34/android/os/FileBridge.java
@@ -0,0 +1,195 @@
+/*
+ * 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.SOCK_STREAM;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Memory;
+import libcore.io.Streams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+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 ParcelFileDescriptor mTarget;
+
+ private ParcelFileDescriptor mServer;
+ private ParcelFileDescriptor mClient;
+
+ private volatile boolean mClosed;
+
+ public FileBridge() {
+ try {
+ ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair(SOCK_STREAM);
+ mServer = fds[0];
+ mClient = fds[1];
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create bridge");
+ }
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public void forceClose() {
+ IoUtils.closeQuietly(mTarget);
+ IoUtils.closeQuietly(mServer);
+ mClosed = true;
+ }
+
+ public void setTargetFile(ParcelFileDescriptor target) {
+ mTarget = target;
+ }
+
+ public ParcelFileDescriptor getClientSocket() {
+ return mClient;
+ }
+
+ @Override
+ public void run() {
+ final ByteBuffer tempBuffer = ByteBuffer.allocateDirect(8192);
+ final byte[] temp = tempBuffer.hasArray() ? tempBuffer.array() : new byte[8192];
+ try {
+ while (IoBridge.read(mServer.getFileDescriptor(), 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.getFileDescriptor(), temp, 0,
+ Math.min(temp.length, len));
+ if (n == -1) {
+ throw new IOException(
+ "Unexpected EOF; still expected " + len + " bytes");
+ }
+ IoBridge.write(mTarget.getFileDescriptor(), temp, 0, n);
+ len -= n;
+ }
+
+ } else if (cmd == CMD_FSYNC) {
+ // Sync and echo back to confirm
+ Os.fsync(mTarget.getFileDescriptor());
+ IoBridge.write(mServer.getFileDescriptor(), temp, 0, MSG_LENGTH);
+
+ } else if (cmd == CMD_CLOSE) {
+ // Close and echo back to confirm
+ Os.fsync(mTarget.getFileDescriptor());
+ mTarget.close();
+ mClosed = true;
+ IoBridge.write(mServer.getFileDescriptor(), 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 ByteBuffer mTempBuffer = ByteBuffer.allocateDirect(MSG_LENGTH);
+ private final byte[] mTemp = mTempBuffer.hasArray()
+ ? mTempBuffer.array()
+ : new byte[MSG_LENGTH];
+
+ public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) {
+ mClientPfd = clientPfd;
+ mClient = clientPfd.getFileDescriptor();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ writeCommandAndBlock(CMD_CLOSE, "close()");
+ } finally {
+ 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-34/android/os/FileObserver.java b/android-34/android/os/FileObserver.java
new file mode 100644
index 0000000..6f44b20
--- /dev/null
+++ b/android-34/android/os/FileObserver.java
@@ -0,0 +1,301 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.util.Log;
+import android.util.SparseArray;
+
+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 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 {
+ /** Temporarily retained; appears to be missing UnsupportedAppUsage annotation */
+ private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
+ private SparseArray<WeakReference> mRealObservers = new SparseArray<>();
+ 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 (mRealObservers) {
+ for (int wfd : wfds) {
+ if (wfd >= 0) {
+ mRealObservers.put(wfd, fileObserverWeakReference);
+ }
+ }
+ }
+
+ return wfds;
+ }
+
+ public void stopWatching(int[] descriptors) {
+ stopWatching(m_fd, descriptors);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void onEvent(int wfd, @NotifyEventType int mask, String path) {
+ // look up our observer, fixing up the map if necessary...
+ FileObserver observer = null;
+
+ synchronized (mRealObservers) {
+ WeakReference weak = mRealObservers.get(wfd);
+ if (weak != null) { // can happen with lots of events from a dead wfd
+ observer = (FileObserver) weak.get();
+ if (observer == null) {
+ mRealObservers.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-34/android/os/FileUriExposedException.java b/android-34/android/os/FileUriExposedException.java
new file mode 100644
index 0000000..a3af24d
--- /dev/null
+++ b/android-34/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 androidx.core.content.FileProvider
+ * @see Intent#FLAG_GRANT_READ_URI_PERMISSION
+ */
+public class FileUriExposedException extends RuntimeException {
+ public FileUriExposedException(String message) {
+ super(message);
+ }
+}
diff --git a/android-34/android/os/FileUtils.java b/android-34/android/os/FileUtils.java
new file mode 100644
index 0000000..af09a06
--- /dev/null
+++ b/android-34/android/os/FileUtils.java
@@ -0,0 +1,1653 @@
+/*
+ * 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.EINVAL;
+import static android.system.OsConstants.ENOSYS;
+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.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.provider.DocumentsContract.Document;
+import android.provider.MediaStore;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.text.TextUtils;
+import android.util.DataUnit;
+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() {
+ }
+
+ private static final String CAMERA_DIR_LOWER_CASE = "/storage/emulated/" + UserHandle.myUserId()
+ + "/dcim/camera";
+
+ /** 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 volatile int sMediaProviderAppId = -1;
+
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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)) {
+ try {
+ return copyInternalSendfile(in, out, count, signal, executor, listener);
+ } catch (ErrnoException e) {
+ if (e.errno == EINVAL || e.errno == ENOSYS) {
+ // sendfile(2) will fail in at least any of the following conditions:
+ // 1. |in| doesn't support mmap(2)
+ // 2. |out| was opened with O_APPEND
+ // We fallback to userspace copy if that fails
+ return copyInternalUserspace(in, out, count, signal, executor,
+ listener);
+ }
+ throw e;
+ }
+ } 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @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
+ */
+ @TestApi
+ @NonNull
+ 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
+ */
+ @TestApi
+ @NonNull
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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} if it
+ * doesn't exist already. Returns a {@code File} object representing the directory if it exists
+ * and {@code null} if not.
+ *
+ * @hide
+ */
+ public static @Nullable File createDir(File baseDir, String name) {
+ final File dir = new File(baseDir, name);
+
+ return createDir(dir) ? dir : null;
+ }
+
+ /**
+ * Ensure the given directory exists, creating it if needed. This method is threadsafe.
+ *
+ * @return false if the directory doesn't exist and couldn't be created
+ *
+ * @hide
+ */
+ public static boolean createDir(File dir) {
+ if (dir.mkdir()) {
+ return true;
+ }
+
+ if (dir.exists()) {
+ return dir.isDirectory();
+ }
+
+ return false;
+ }
+
+ /**
+ * 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.
+ *
+ * Some storage devices are still using GiB (powers of 1024) over
+ * GB (powers of 1000) measurements and this method takes it into account.
+ *
+ * Round ranges:
+ * ...
+ * [256 GiB + 1; 512 GiB] -> 512 GB
+ * [512 GiB + 1; 1 TiB] -> 1 TB
+ * [1 TiB + 1; 2 TiB] -> 2 TB
+ * etc
+ *
+ * @hide
+ */
+ public static long roundStorageSize(long size) {
+ long val = 1;
+ long kiloPow = 1;
+ long kibiPow = 1;
+ while ((val * kibiPow) < size) {
+ val <<= 1;
+ if (val > 512) {
+ val = 1;
+ kibiPow *= 1024;
+ kiloPow *= 1000;
+ }
+ }
+ return val * kiloPow;
+ }
+
+ private static long toBytes(long value, String unit) {
+ unit = unit.toUpperCase();
+
+ if ("B".equals(unit)) {
+ return value;
+ }
+
+ if ("K".equals(unit) || "KB".equals(unit)) {
+ return DataUnit.KILOBYTES.toBytes(value);
+ }
+
+ if ("M".equals(unit) || "MB".equals(unit)) {
+ return DataUnit.MEGABYTES.toBytes(value);
+ }
+
+ if ("G".equals(unit) || "GB".equals(unit)) {
+ return DataUnit.GIGABYTES.toBytes(value);
+ }
+
+ if ("KI".equals(unit) || "KIB".equals(unit)) {
+ return DataUnit.KIBIBYTES.toBytes(value);
+ }
+
+ if ("MI".equals(unit) || "MIB".equals(unit)) {
+ return DataUnit.MEBIBYTES.toBytes(value);
+ }
+
+ if ("GI".equals(unit) || "GIB".equals(unit)) {
+ return DataUnit.GIBIBYTES.toBytes(value);
+ }
+
+ return Long.MIN_VALUE;
+ }
+
+ /**
+ * @param fmtSize The string that contains the size to be parsed. The
+ * expected format is:
+ *
+ * <p>"^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$"
+ *
+ * <p>For example: 10Kb, 500GiB, 100mb. The unit is not case sensitive.
+ *
+ * @return the size in bytes. If {@code fmtSize} has invalid format, it
+ * returns {@link Long#MIN_VALUE}.
+ * @hide
+ */
+ public static long parseSize(@Nullable String fmtSize) {
+ if (fmtSize == null || fmtSize.isBlank()) {
+ return Long.MIN_VALUE;
+ }
+
+ int sign = 1;
+ fmtSize = fmtSize.trim();
+ char first = fmtSize.charAt(0);
+ if (first == '-' || first == '+') {
+ if (first == '-') {
+ sign = -1;
+ }
+
+ fmtSize = fmtSize.substring(1);
+ }
+
+ int index = 0;
+ // Find the last index of the value in fmtSize.
+ while (index < fmtSize.length() && Character.isDigit(fmtSize.charAt(index))) {
+ index++;
+ }
+
+ // Check if number and units are present.
+ if (index == 0 || index == fmtSize.length()) {
+ return Long.MIN_VALUE;
+ }
+
+ long value = sign * Long.valueOf(fmtSize.substring(0, index));
+ String unit = fmtSize.substring(index).trim();
+
+ return toBytes(value, unit);
+ }
+
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ *
+ * @deprecated This method may suppress potentially significant exceptions, particularly when
+ * closing writable resources. With a writable resource, a failure thrown from {@code close()}
+ * should be considered as significant as a failure thrown from a write method because it may
+ * indicate a failure to flush bytes to the underlying resource.
+ */
+ @Deprecated
+ 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}.
+ *
+ * @deprecated This method may suppress potentially significant exceptions, particularly when
+ * closing writable resources. With a writable resource, a failure thrown from {@code close()}
+ * should be considered as significant as a failure thrown from a write method because it may
+ * indicate a failure to flush bytes to the underlying resource.
+ */
+ @Deprecated
+ public static void closeQuietly(@Nullable FileDescriptor fd) {
+ IoUtils.closeQuietly(fd);
+ }
+
+ /** {@hide} */
+ public static int translateModeStringToPosix(String mode) {
+ // Quick 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 ParcelFileDescriptor convertToModernFd(FileDescriptor fd) {
+ Context context = AppGlobals.getInitialApplication();
+ if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
+ // Never convert modern fd for MediaProvider, because this requires
+ // MediaStore#scanFile and can cause infinite loops when MediaProvider scans
+ return null;
+ }
+
+ try (ParcelFileDescriptor dupFd = ParcelFileDescriptor.dup(fd)) {
+ return MediaStore.getOriginalMediaFormatFileDescriptor(context, dupFd);
+ } catch (Exception e) {
+ // Ignore error
+ return null;
+ }
+ }
+
+ private static int getMediaProviderAppId(Context context) {
+ if (sMediaProviderAppId != -1) {
+ return sMediaProviderAppId;
+ }
+
+ PackageManager pm = context.getPackageManager();
+ ProviderInfo provider = context.getPackageManager().resolveContentProvider(
+ MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (provider == null) {
+ return -1;
+ }
+
+ sMediaProviderAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+ return sMediaProviderAppId;
+ }
+
+ /** {@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-34/android/os/GraphicsEnvironment.java b/android-34/android/os/GraphicsEnvironment.java
new file mode 100644
index 0000000..94971b8
--- /dev/null
+++ b/android-34/android/os/GraphicsEnvironment.java
@@ -0,0 +1,915 @@
+/*
+ * 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.app.GameManager;
+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.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import dalvik.system.VMRuntime;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * GraphicsEnvironment sets up necessary properties for the graphics environment of the
+ * application process.
+ * GraphicsEnvironment uses a bunch of settings global variables to determine the setup,
+ * the change of settings global variables will only take effect before setup() is called,
+ * and any subsequent change will not impact the current running processes.
+ *
+ * @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 ANGLE_DRIVER_NAME = "angle";
+ private static final String ANGLE_DRIVER_VERSION_NAME = "";
+ private static final long ANGLE_DRIVER_VERSION_CODE = 0;
+
+ // System properties related to updatable graphics drivers.
+ private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "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";
+
+ // Metadata flags within the <application> tag in the AndroidManifest.xml file.
+ private static final String METADATA_DRIVER_BUILD_TIME =
+ "com.android.graphics.driver.build_time";
+ private static final String METADATA_DEVELOPER_DRIVER_ENABLE =
+ "com.android.graphics.developerdriver.enable";
+ private static final String METADATA_INJECT_LAYERS_ENABLE =
+ "com.android.graphics.injectLayers.enable";
+
+ private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
+ private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+
+ 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 int VULKAN_1_0 = 0x00400000;
+ private static final int VULKAN_1_1 = 0x00401000;
+ private static final int VULKAN_1_2 = 0x00402000;
+ private static final int VULKAN_1_3 = 0x00403000;
+
+ // Values for UPDATABLE_DRIVER_ALL_APPS
+ // 0: Default (Invalid values fallback to default as well)
+ // 1: All apps use updatable production driver
+ // 2: All apps use updatable prerelease driver
+ // 3: All apps use system graphics driver
+ private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT = 0;
+ private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER = 1;
+ private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
+ private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
+
+ // Values for ANGLE_GL_DRIVER_ALL_ANGLE
+ private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1;
+ private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
+
+ // Values for ANGLE_GL_DRIVER_SELECTION_VALUES
+ private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
+ private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
+ private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+
+ private ClassLoader mClassLoader;
+ private String mLibrarySearchPaths;
+ private String mLibraryPermittedPaths;
+ private GameManager mGameManager;
+
+ private int mAngleOptInIndex = -1;
+ private boolean mEnabledByGameMode = false;
+
+ /**
+ * Set up GraphicsEnvironment
+ */
+ public void setup(Context context, Bundle coreSettings) {
+ final PackageManager pm = context.getPackageManager();
+ final String packageName = context.getPackageName();
+ final ApplicationInfo appInfoWithMetaData =
+ getAppInfoWithMetadata(context, pm, packageName);
+
+ mGameManager = context.getSystemService(GameManager.class);
+
+ Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
+ setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
+ Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
+ // Setup ANGLE and pass down ANGLE details to the C++ code
+ Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
+ boolean useAngle = false;
+ if (setupAngle(context, coreSettings, pm, packageName)) {
+ if (shouldUseAngle(context, coreSettings, packageName)) {
+ useAngle = true;
+ setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+ 0, packageName, getVulkanVersion(pm));
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
+ Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
+ if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
+ if (!useAngle) {
+ 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);
+
+ Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "notifyGraphicsEnvironmentSetup");
+ if (mGameManager != null
+ && appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
+ mGameManager.notifyGraphicsEnvironmentSetup();
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+ }
+
+ /**
+ * Switch the system to use ANGLE as the default GLES driver.
+ */
+ public void toggleAngleAsSystemDriver(boolean enabled) {
+ nativeToggleAngleAsSystemDriver(enabled);
+ }
+
+ /**
+ * Query to determine if the Game Mode has enabled ANGLE.
+ */
+ private boolean isAngleEnabledByGameMode(Context context, String packageName) {
+ try {
+ final boolean gameModeEnabledAngle =
+ (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
+ Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
+ return gameModeEnabledAngle;
+ } catch (SecurityException e) {
+ Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
+ + "for package: " + packageName);
+ }
+
+ return false;
+ }
+
+ /**
+ * Query to determine if ANGLE should be used
+ */
+ private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
+ if (TextUtils.isEmpty(packageName)) {
+ Log.v(TAG, "No package name specified; use the system driver");
+ return false;
+ }
+
+ return shouldUseAngleInternal(context, coreSettings, packageName);
+ }
+
+ private 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_3)) {
+ return VULKAN_1_3;
+ }
+
+ if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_2)) {
+ return VULKAN_1_2;
+ }
+
+ 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 has set the manifest metadata for layer injection.
+ */
+ private boolean canInjectLayers(ApplicationInfo ai) {
+ return (ai.metaData != null && ai.metaData.getBoolean(METADATA_INJECT_LAYERS_ENABLE)
+ && setInjectLayersPrSetDumpable());
+ }
+
+ /**
+ * Store the class loader for namespace lookup later.
+ */
+ public void setLayerPaths(ClassLoader classLoader,
+ String searchPaths,
+ String permittedPaths) {
+ // 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;
+ mLibrarySearchPaths = searchPaths;
+ mLibraryPermittedPaths = permittedPaths;
+ }
+
+ /**
+ * Returns the debug layer paths from settings.
+ * Returns null if:
+ * 1) The application process is not debuggable or layer injection metadata flag is not
+ * true; Or
+ * 2) ENABLE_GPU_DEBUG_LAYERS is not true; Or
+ * 3) Package name is not equal to GPU_DEBUG_APP.
+ */
+ public String getDebugLayerPathsFromSettings(
+ Bundle coreSettings, IPackageManager pm, String packageName,
+ ApplicationInfo ai) {
+ if (!debugLayerEnabled(coreSettings, packageName, ai)) {
+ return null;
+ }
+ Log.i(TAG, "GPU debug layers enabled for " + packageName);
+ String debugLayerPaths = "";
+
+ // Grab all debug layer apps and add to paths.
+ final String gpuDebugLayerApps =
+ coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP, "");
+ if (!gpuDebugLayerApps.isEmpty()) {
+ Log.i(TAG, "GPU debug layer apps: " + gpuDebugLayerApps);
+ // If a colon is present, treat this as multiple apps, so Vulkan and GLES
+ // layer apps can be provided at the same time.
+ final String[] layerApps = gpuDebugLayerApps.split(":");
+ for (int i = 0; i < layerApps.length; i++) {
+ String paths = getDebugLayerAppPaths(pm, layerApps[i]);
+ if (!paths.isEmpty()) {
+ // Append the path so files placed in the app's base directory will
+ // override the external path
+ debugLayerPaths += paths + File.pathSeparator;
+ }
+ }
+ }
+ return debugLayerPaths;
+ }
+
+ /**
+ * Return the debug layer app's on-disk and in-APK lib directories
+ */
+ private String getDebugLayerAppPaths(IPackageManager pm, String packageName) {
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ return "";
+ }
+ if (appInfo == null) {
+ Log.w(TAG, "Debug layer app '" + packageName + "' not installed");
+ return "";
+ }
+
+ final String abi = chooseAbi(appInfo);
+ final StringBuilder sb = new StringBuilder();
+ sb.append(appInfo.nativeLibraryDir)
+ .append(File.pathSeparator)
+ .append(appInfo.sourceDir)
+ .append("!/lib/")
+ .append(abi);
+ final String paths = sb.toString();
+ if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths);
+
+ return paths;
+ }
+
+ private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) {
+ // Only enable additional debug functionality if the following conditions are met:
+ // 1. App is debuggable or device is rooted or layer injection metadata flag is true
+ // 2. ENABLE_GPU_DEBUG_LAYERS is true
+ // 3. Package name is equal to GPU_DEBUG_APP
+ if (!isDebuggable() && !canInjectLayers(ai)) {
+ return false;
+ }
+ final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
+ if (enable == 0) {
+ return false;
+ }
+ final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, "");
+ if (packageName == null
+ || (gpuDebugApp.isEmpty() || packageName.isEmpty())
+ || !gpuDebugApp.equals(packageName)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set up layer search paths for all apps
+ */
+ private void setupGpuLayers(
+ Context context, Bundle coreSettings, PackageManager pm, String packageName,
+ ApplicationInfo ai) {
+ final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai);
+ String layerPaths = "";
+ if (enabled) {
+ layerPaths = mLibraryPermittedPaths;
+
+ 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 += mLibrarySearchPaths;
+ setLayerPaths(mClassLoader, layerPaths);
+ }
+
+ 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 getPackageIndex(String packageName, List<String> packages) {
+ for (int idx = 0; idx < packages.size(); idx++) {
+ if (packages.get(idx).equals(packageName)) {
+ return idx;
+ }
+ }
+
+ return -1;
+ }
+
+ private static ApplicationInfo getAppInfoWithMetadata(Context context,
+ PackageManager pm, String packageName) {
+ ApplicationInfo ai;
+ try {
+ // Get the ApplicationInfo from PackageManager so that metadata fields present.
+ ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Unlikely to fail for applications, but in case of failure, fall back to use the
+ // ApplicationInfo from context directly.
+ ai = context.getApplicationInfo();
+ }
+ return ai;
+ }
+
+ /*
+ * Determine which GLES "driver" should be used for the package, taking into account the
+ * following factors (in priority order):
+ *
+ * 1) The semi-global switch (i.e. Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE; which is set by
+ * the "angle_gl_driver_all_angle" setting; which forces a driver for all processes that
+ * start after the Java run time is up), if it forces a choice;
+ * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and
+ * Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the
+ * “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it
+ * forces a choice;
+ * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
+ */
+ private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+ // Make sure we have a good package name
+ if (TextUtils.isEmpty(packageName)) {
+ return false;
+ }
+
+ // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
+ // should be forced on or off for "all appplications"
+ final int allUseAngle;
+ if (bundle != null) {
+ allUseAngle = bundle.getInt(Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE);
+ } else {
+ ContentResolver contentResolver = context.getContentResolver();
+ allUseAngle = Settings.Global.getInt(contentResolver,
+ Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE, ANGLE_GL_DRIVER_ALL_ANGLE_OFF);
+ }
+ if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
+ Log.v(TAG, "Turn on ANGLE for all applications.");
+ return true;
+ }
+
+ // Get the per-application settings lists
+ final ContentResolver contentResolver = context.getContentResolver();
+ final List<String> optInPackages = getGlobalSettingsString(
+ contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS);
+ final List<String> optInValues = getGlobalSettingsString(
+ contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES);
+ Log.v(TAG, "Currently set values for:");
+ Log.v(TAG, " angle_gl_driver_selection_pkgs=" + optInPackages);
+ Log.v(TAG, " angle_gl_driver_selection_values=" + optInValues);
+
+ mEnabledByGameMode = isAngleEnabledByGameMode(context, packageName);
+
+ // Make sure we have good settings to use
+ if (optInPackages.size() != optInValues.size()) {
+ Log.v(TAG,
+ "Global.Settings values are invalid: "
+ + "number of packages: "
+ + optInPackages.size() + ", "
+ + "number of values: "
+ + optInValues.size());
+ return mEnabledByGameMode;
+ }
+
+ // See if this application is listed in the per-application settings list
+ final int pkgIndex = getPackageIndex(packageName, optInPackages);
+
+ if (pkgIndex < 0) {
+ Log.v(TAG, packageName + " is not listed in per-application setting");
+ return mEnabledByGameMode;
+ }
+ mAngleOptInIndex = pkgIndex;
+
+ // The application IS listed in the per-application settings list; and so use the
+ // setting--choosing the current system driver if the setting is "default"
+ String optInValue = optInValues.get(pkgIndex);
+ Log.v(TAG,
+ "ANGLE Developer option for '" + packageName + "' "
+ + "set to: '" + optInValue + "'");
+ if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
+ return true;
+ } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+ return false;
+ } else {
+ // The user either chose default or an invalid value; go with the default driver or what
+ // the game mode indicates
+ return mEnabledByGameMode;
+ }
+ }
+
+ /**
+ * 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.
+ * An application can load ANGLE debug package if it is a debuggable application, or
+ * the device is debuggable.
+ */
+ private String getAngleDebugPackage(Context context, Bundle coreSettings) {
+ if (!isDebuggable()) {
+ return "";
+ }
+ final String debugPackage;
+
+ if (coreSettings != null) {
+ debugPackage =
+ coreSettings.getString(Settings.Global.ANGLE_DEBUG_PACKAGE);
+ } else {
+ ContentResolver contentResolver = context.getContentResolver();
+ debugPackage = Settings.Global.getString(contentResolver,
+ Settings.Global.ANGLE_DEBUG_PACKAGE);
+ }
+ if (TextUtils.isEmpty(debugPackage)) {
+ return "";
+ }
+ return debugPackage;
+ }
+
+ /**
+ * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to
+ * the C++ GraphicsEnv class.
+ *
+ * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be
+ * properly used.
+ *
+ * @param context
+ * @param bundle
+ * @param pm
+ * @param packageName - package name of the application.
+ * @return true: ANGLE setup successfully
+ * false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
+ */
+ private 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.v(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) {
+ // If the debug package is specified but not found, abort.
+ Log.v(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 (TextUtils.isEmpty(anglePkgName)) {
+ Log.v(TAG, "Failed to find ANGLE package.");
+ return false;
+ }
+
+ Log.v(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.v(TAG, "ANGLE package '" + anglePkgName + "' not installed");
+ 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.d(TAG, "ANGLE package libs: " + paths);
+ }
+
+ // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
+ // and features to use.
+ final String[] features = getAngleEglFeatures(context, bundle);
+ setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
+
+ return true;
+ }
+
+ /**
+ * 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.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);
+ }
+ }
+
+ private String[] getAngleEglFeatures(Context context, Bundle coreSettings) {
+ if (mAngleOptInIndex < 0) {
+ return null;
+ }
+
+ final List<String> featuresLists = getGlobalSettingsString(
+ context.getContentResolver(), coreSettings, Settings.Global.ANGLE_EGL_FEATURES);
+ if (featuresLists.size() <= mAngleOptInIndex) {
+ return null;
+ }
+ return featuresLists.get(mAngleOptInIndex).split(":");
+ }
+
+ /**
+ * Return the driver package name to use. Return null for system driver.
+ */
+ private String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
+ final String productionDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRODUCTION);
+ final boolean hasProductionDriver = productionDriver != null && !productionDriver.isEmpty();
+
+ final String prereleaseDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE);
+ final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
+
+ if (!hasProductionDriver && !hasPrereleaseDriver) {
+ Log.v(TAG, "Neither updatable production driver nor prerelease driver is supported.");
+ return null;
+ }
+
+ // To minimize risk of driver updates crippling the device beyond user repair, never use the
+ // updatable drivers for privileged or non-updated system apps. Presumably pre-installed
+ // apps were tested thoroughly with the system driver.
+ if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
+ if (DEBUG) {
+ Log.v(TAG,
+ "Ignore updatable driver package for privileged/non-updated system app.");
+ }
+ return null;
+ }
+
+ final boolean enablePrereleaseDriver =
+ (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE))
+ || isDebuggable();
+
+ // Priority of updatable driver settings on confliction (Higher priority comes first):
+ // 1. UPDATABLE_DRIVER_ALL_APPS
+ // 2. UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS
+ // 3. UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS
+ // 4. UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS
+ // 5. UPDATABLE_DRIVER_PRODUCTION_DENYLIST
+ // 6. UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST
+ switch (coreSettings.getInt(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, 0)) {
+ case UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF:
+ Log.v(TAG, "The updatable driver is turned off on this device.");
+ return null;
+ case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER:
+ Log.v(TAG, "All apps opt in to use updatable production driver.");
+ return hasProductionDriver ? productionDriver : null;
+ case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
+ Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
+ return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
+ case UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT:
+ default:
+ break;
+ }
+
+ final String appPackageName = ai.packageName;
+ if (getGlobalSettingsString(null, coreSettings,
+ Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS)
+ .contains(appPackageName)) {
+ Log.v(TAG, "App opts out for updatable production driver.");
+ return null;
+ }
+
+ if (getGlobalSettingsString(
+ null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS)
+ .contains(appPackageName)) {
+ Log.v(TAG, "App opts in for updatable prerelease driver.");
+ return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
+ }
+
+ // Early return here since the rest logic is only for updatable production Driver.
+ if (!hasProductionDriver) {
+ Log.v(TAG, "Updatable production driver is not supported on the device.");
+ return null;
+ }
+
+ final boolean isOptIn =
+ getGlobalSettingsString(null, coreSettings,
+ Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS)
+ .contains(appPackageName);
+ final List<String> allowlist =
+ getGlobalSettingsString(null, coreSettings,
+ Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
+ if (!isOptIn && allowlist.indexOf(UPDATABLE_DRIVER_ALLOWLIST_ALL) != 0
+ && !allowlist.contains(appPackageName)) {
+ Log.v(TAG, "App is not on the allowlist for updatable production driver.");
+ return null;
+ }
+
+ // If the application is not opted-in, then check whether it's on the denylist,
+ // terminate early if it's on the denylist and fallback to system driver.
+ if (!isOptIn
+ && getGlobalSettingsString(
+ null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST)
+ .contains(appPackageName)) {
+ Log.v(TAG, "App is on the denylist for updatable production driver.");
+ return null;
+ }
+
+ return productionDriver;
+ }
+
+ /**
+ * Choose whether the current process should use the builtin or an updated driver.
+ */
+ private boolean chooseDriver(
+ Context context, Bundle coreSettings, PackageManager pm, String packageName,
+ ApplicationInfo ai) {
+ final String driverPackageName = chooseDriverInternal(coreSettings, ai);
+ 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, "updatable 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, "updatable driver package is not 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, "Updatable 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);
+ Log.v(TAG, "Updatable 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");
+ }
+
+ String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
+ if (driverBuildTime == null || driverBuildTime.length() <= 1) {
+ Log.w(TAG, "com.android.graphics.driver.build_time is not set");
+ driverBuildTime = "L0";
+ }
+ // 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 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(UPDATABLE_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 '" + UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME + "'");
+ }
+ }
+ return "";
+ }
+
+ private static native boolean isDebuggable();
+ 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, String[] features);
+ private static native boolean getShouldUseAngle(String packageName);
+ private static native boolean setInjectLayersPrSetDumpable();
+ private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
+
+ /**
+ * 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();
+}
diff --git a/android-34/android/os/Handler.java b/android-34/android/os/Handler.java
new file mode 100644
index 0000000..ceaf337
--- /dev/null
+++ b/android-34/android/os/Handler.java
@@ -0,0 +1,1023 @@
+/*
+ * 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.compat.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 a {@link Looper}.
+ * It will deliver messages and runnables to that Looper's message
+ * queue and execute them on that Looper's thread.
+ *
+ * <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.
+ *
+ * @deprecated Implicitly choosing a Looper during Handler construction can lead to bugs
+ * where operations are silently lost (if the Handler is not expecting new tasks and quits),
+ * crashes (if a handler is sometimes created on a thread without a Looper active), or race
+ * conditions, where the thread a handler is associated with is not what the author
+ * anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper
+ * explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or
+ * similar. If the implicit thread local behavior is required for compatibility, use
+ * {@code new Handler(Looper.myLooper())} to make it clear to readers.
+ *
+ */
+ @Deprecated
+ 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.
+ *
+ * @deprecated Implicitly choosing a Looper during Handler construction can lead to bugs
+ * where operations are silently lost (if the Handler is not expecting new tasks and quits),
+ * crashes (if a handler is sometimes created on a thread without a Looper active), or race
+ * conditions, where the thread a handler is associated with is not what the author
+ * anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper
+ * explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or
+ * similar. If the implicit thread local behavior is required for compatibility, use
+ * {@code new Handler(Looper.myLooper(), callback)} to make it clear to readers.
+ */
+ @Deprecated
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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;
+ mIsShared = false;
+ }
+
+ /**
+ * 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) {
+ this(looper, callback, async, /* shared= */ false);
+ }
+
+ /** @hide */
+ public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
+ boolean shared) {
+ mLooper = looper;
+ mQueue = looper.mQueue;
+ mCallback = callback;
+ mAsynchronous = async;
+ mIsShared = shared;
+ }
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @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) {
+ if (message.callback instanceof TraceNameSupplier) {
+ return ((TraceNameSupplier) message.callback).getTraceName();
+ }
+
+ 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);
+ }
+
+ private Object disallowNullArgumentIfShared(@Nullable Object arg) {
+ if (mIsShared && arg == null) {
+ throw new IllegalArgumentException("Null argument disallowed for shared handler."
+ + " Consider creating your own Handler instance.");
+ }
+ return arg;
+ }
+
+ /**
+ * 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, disallowNullArgumentIfShared(object));
+ }
+
+ /**
+ * 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.
+ * <p>
+ * Similar to {@link #removeMessages(int, Object)} but uses object equality
+ * ({@link Object#equals(Object)}) instead of reference equality (==) in
+ * determining whether object is the message's obj'.
+ *
+ *@hide
+ */
+ public final void removeEqualMessages(int what, @Nullable Object object) {
+ mQueue.removeEqualMessages(this, what, disallowNullArgumentIfShared(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, disallowNullArgumentIfShared(token));
+ }
+
+ /**
+ * 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.
+ *
+ *@hide
+ */
+ public final void removeCallbacksAndEqualMessages(@Nullable Object token) {
+ mQueue.removeCallbacksAndEqualMessages(this, disallowNullArgumentIfShared(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 code 'what' and
+ * whose obj is 'object' in the message queue.
+ *
+ *@hide
+ */
+ public final boolean hasEqualMessages(int what, @Nullable Object object) {
+ return mQueue.hasEqualMessages(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;
+
+ /** If it's a shared handler, we disallow certain dangeraous operations. */
+ private final boolean mIsShared;
+
+ 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-34/android/os/HandlerExecutor.java b/android-34/android/os/HandlerExecutor.java
new file mode 100644
index 0000000..416b24b
--- /dev/null
+++ b/android-34/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-34/android/os/HandlerThread.java b/android-34/android/os/HandlerThread.java
new file mode 100644
index 0000000..4dd797a
--- /dev/null
+++ b/android-34/android/os/HandlerThread.java
@@ -0,0 +1,179 @@
+/*
+ * 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;
+ }
+
+ boolean wasInterrupted = false;
+
+ // If the thread has been started, wait until the looper has been created.
+ synchronized (this) {
+ while (isAlive() && mLooper == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ wasInterrupted = true;
+ }
+ }
+ }
+
+ /*
+ * We may need to restore the thread's interrupted flag, because it may
+ * have been cleared above since we eat InterruptedExceptions
+ */
+ if (wasInterrupted) {
+ Thread.currentThread().interrupt();
+ }
+
+ 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-34/android/os/HardwarePropertiesManager.java b/android-34/android/os/HardwarePropertiesManager.java
new file mode 100644
index 0000000..3d072c5
--- /dev/null
+++ b/android-34/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-34/android/os/HidlMemory.java b/android-34/android/os/HidlMemory.java
new file mode 100644
index 0000000..2539a6b
--- /dev/null
+++ b/android-34/android/os/HidlMemory.java
@@ -0,0 +1,140 @@
+/*
+ * 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.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * An abstract representation of a memory block, as representing by the HIDL system.
+ *
+ * The block is defined by a {name, size, handle} tuple, where the name is used to determine how to
+ * interpret the handle. The underlying handle is assumed to be owned by this instance and will be
+ * closed as soon as {@link #close()} is called on this instance, or this instance has been
+ * finalized (the latter supports using it in a shared manner without having to worry about who owns
+ * this instance, the former is more efficient resource-wise and is recommended for most use-cases).
+ * Note, however, that ownership of the handle does not necessarily imply ownership of the
+ * underlying file descriptors - the underlying handle may or may not own them. If you want the
+ * underlying handle to outlive this instance, call {@link #releaseHandle()} to obtain the handle
+ * and detach the ownership relationship.
+ *
+ * @hide
+ */
+@SystemApi
+public class HidlMemory implements Closeable {
+ private final @NonNull String mName;
+ private final long mSize;
+ private @Nullable NativeHandle mHandle;
+ private long mNativeContext; // For use of native code.
+
+ /**
+ * Constructor.
+ *
+ * @param name The name of the IMapper service used to resolve the handle (e.g. "ashmem").
+ * @param size The (non-negative) size in bytes of the memory block.
+ * @param handle The handle. May be null. This instance will own the handle and will close it
+ * as soon as {@link #close()} is called or the object is destroyed. This, this
+ * handle instance should generally not be shared with other clients.
+ */
+ public HidlMemory(@NonNull String name, @IntRange(from = 0) long size,
+ @Nullable NativeHandle handle) {
+ mName = name;
+ mSize = size;
+ mHandle = handle;
+ }
+
+ /**
+ * Create a copy of this instance, where the underlying handle (and its file descriptors) have
+ * been duplicated.
+ */
+ @NonNull
+ public HidlMemory dup() throws IOException {
+ return new HidlMemory(mName, mSize, mHandle != null ? mHandle.dup() : null);
+ }
+
+ /**
+ * Close the underlying native handle. No-op if handle is null or has been released using {@link
+ * #releaseHandle()}.
+ */
+ @Override
+ public void close() throws IOException {
+ if (mHandle != null) {
+ mHandle.close();
+ mHandle = null;
+ }
+ }
+
+ /**
+ * Disowns the underlying handle and returns it. The underlying handle becomes null.
+ *
+ * @return The underlying handle.
+ */
+ @Nullable
+ public NativeHandle releaseHandle() {
+ NativeHandle handle = mHandle;
+ mHandle = null;
+ return handle;
+ }
+
+ /**
+ * Gets the name, which represents how the handle is to be interpreted.
+ *
+ * @return The name.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the size of the block, in bytes.
+ *
+ * @return The size.
+ */
+ public long getSize() {
+ return mSize;
+ }
+
+ /**
+ * Gets a native handle. The actual interpretation depends on the name and is implementation
+ * defined.
+ *
+ * @return The native handle.
+ */
+ @Nullable
+ public NativeHandle getHandle() {
+ return mHandle;
+ }
+
+ @Override
+ protected void finalize() {
+ try {
+ close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ nativeFinalize();
+ }
+ }
+
+ private native void nativeFinalize();
+}
diff --git a/android-34/android/os/HidlMemoryUtil.java b/android-34/android/os/HidlMemoryUtil.java
new file mode 100644
index 0000000..a1b2aef
--- /dev/null
+++ b/android-34/android/os/HidlMemoryUtil.java
@@ -0,0 +1,246 @@
+/*
+ * 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 static android.system.OsConstants.MAP_SHARED;
+import static android.system.OsConstants.PROT_READ;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+import java.nio.DirectByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides utilities for dealing with HidlMemory.
+ *
+ * @hide
+ */
+public final class HidlMemoryUtil {
+ static private final String TAG = "HidlMemoryUtil";
+
+ private HidlMemoryUtil() {
+ }
+
+ /**
+ * Copies a byte-array into a new Ashmem region and return it as HidlMemory.
+ * The returned instance owns the underlying file descriptors, and the client should generally
+ * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
+ * be closed).
+ *
+ * @param input The input byte array.
+ * @return A HidlMemory instance, containing a copy of the input.
+ */
+ public static @NonNull
+ HidlMemory byteArrayToHidlMemory(@NonNull byte[] input) {
+ return byteArrayToHidlMemory(input, null);
+ }
+
+ /**
+ * Copies a byte-array into a new Ashmem region and return it as HidlMemory.
+ * The returned instance owns the underlying file descriptors, and the client should generally
+ * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
+ * be closed).
+ *
+ * @param input The input byte array.
+ * @param name An optional name for the ashmem region.
+ * @return A HidlMemory instance, containing a copy of the input.
+ */
+ public static @NonNull
+ HidlMemory byteArrayToHidlMemory(@NonNull byte[] input, @Nullable String name) {
+ Preconditions.checkNotNull(input);
+
+ if (input.length == 0) {
+ return new HidlMemory("ashmem", 0, null);
+ }
+
+ try (SharedMemory shmem = SharedMemory.create(name != null ? name : "", input.length)) {
+ ByteBuffer buffer = shmem.mapReadWrite();
+ buffer.put(input);
+ shmem.unmap(buffer);
+ return sharedMemoryToHidlMemory(shmem);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Copies a byte list into a new Ashmem region and return it as HidlMemory.
+ * The returned instance owns the underlying file descriptors, and the client should generally
+ * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
+ * be closed).
+ *
+ * @param input The input byte list.
+ * @return A HidlMemory instance, containing a copy of the input.
+ */
+ public static @NonNull
+ HidlMemory byteListToHidlMemory(@NonNull List<Byte> input) {
+ return byteListToHidlMemory(input, null);
+ }
+
+ /**
+ * Copies a byte list into a new Ashmem region and return it as HidlMemory.
+ * The returned instance owns the underlying file descriptors, and the client should generally
+ * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
+ * be closed).
+ *
+ * @param input The input byte list.
+ * @param name An optional name for the ashmem region.
+ * @return A HidlMemory instance, containing a copy of the input.
+ */
+ public static @NonNull
+ HidlMemory byteListToHidlMemory(@NonNull List<Byte> input, @Nullable String name) {
+ Preconditions.checkNotNull(input);
+
+ if (input.isEmpty()) {
+ return new HidlMemory("ashmem", 0, null);
+ }
+
+ try (SharedMemory shmem = SharedMemory.create(name != null ? name : "", input.size())) {
+ ByteBuffer buffer = shmem.mapReadWrite();
+ for (Byte b : input) {
+ buffer.put(b);
+ }
+ shmem.unmap(buffer);
+ return sharedMemoryToHidlMemory(shmem);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Copies all data from a HidlMemory instance into a byte array.
+ *
+ * @param mem The HidlMemory instance. Must be of name "ashmem" and of size that doesn't exceed
+ * {@link Integer#MAX_VALUE}.
+ * @return A byte array, containing a copy of the input.
+ */
+ public static @NonNull
+ byte[] hidlMemoryToByteArray(@NonNull HidlMemory mem) {
+ Preconditions.checkNotNull(mem);
+ Preconditions.checkArgumentInRange(mem.getSize(), 0L, (long) Integer.MAX_VALUE,
+ "Memory size");
+ Preconditions.checkArgument(mem.getSize() == 0 || mem.getName().equals("ashmem"),
+ "Unsupported memory type: %s", mem.getName());
+
+ if (mem.getSize() == 0) {
+ return new byte[0];
+ }
+
+ ByteBuffer buffer = getBuffer(mem);
+ byte[] result = new byte[buffer.remaining()];
+ buffer.get(result);
+ return result;
+ }
+
+ /**
+ * Copies all data from a HidlMemory instance into a byte list.
+ *
+ * @param mem The HidlMemory instance. Must be of name "ashmem" and of size that doesn't exceed
+ * {@link Integer#MAX_VALUE}.
+ * @return A byte list, containing a copy of the input.
+ */
+ @SuppressLint("ConcreteCollection")
+ public static @NonNull
+ ArrayList<Byte> hidlMemoryToByteList(@NonNull HidlMemory mem) {
+ Preconditions.checkNotNull(mem);
+ Preconditions.checkArgumentInRange(mem.getSize(), 0L, (long) Integer.MAX_VALUE,
+ "Memory size");
+ Preconditions.checkArgument(mem.getSize() == 0 || mem.getName().equals("ashmem"),
+ "Unsupported memory type: %s", mem.getName());
+
+ if (mem.getSize() == 0) {
+ return new ArrayList<>();
+ }
+
+ ByteBuffer buffer = getBuffer(mem);
+
+ ArrayList<Byte> result = new ArrayList<>(buffer.remaining());
+ while (buffer.hasRemaining()) {
+ result.add(buffer.get());
+ }
+ return result;
+ }
+
+ /**
+ * Converts a SharedMemory to a HidlMemory without copying.
+ *
+ * @param shmem The shared memory object. Null means "empty" and will still result in a non-null
+ * return value.
+ * @return The HidlMemory instance.
+ */
+ @NonNull public static HidlMemory sharedMemoryToHidlMemory(@Nullable SharedMemory shmem) {
+ if (shmem == null) {
+ return new HidlMemory("ashmem", 0, null);
+ }
+ return fileDescriptorToHidlMemory(shmem.getFileDescriptor(), shmem.getSize());
+ }
+
+ /**
+ * Converts a FileDescriptor to a HidlMemory without copying.
+ *
+ * @param fd The FileDescriptor object. Null is allowed if size is 0 and will still result in
+ * a non-null return value.
+ * @param size The size of the memory buffer.
+ * @return The HidlMemory instance.
+ */
+ @NonNull public static HidlMemory fileDescriptorToHidlMemory(@Nullable FileDescriptor fd,
+ int size) {
+ Preconditions.checkArgument(fd != null || size == 0);
+ if (fd == null) {
+ return new HidlMemory("ashmem", 0, null);
+ }
+ try {
+ NativeHandle handle = new NativeHandle(Os.dup(fd), true);
+ return new HidlMemory("ashmem", size, handle);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static ByteBuffer getBuffer(@NonNull HidlMemory mem) {
+ try {
+ final int size = (int) mem.getSize();
+
+ if (size == 0) {
+ return ByteBuffer.wrap(new byte[0]);
+ }
+
+ NativeHandle handle = mem.getHandle();
+
+ final long address = Os.mmap(0, size, PROT_READ, MAP_SHARED, handle.getFileDescriptor(),
+ 0);
+ return new DirectByteBuffer(size, address, handle.getFileDescriptor(), () -> {
+ try {
+ Os.munmap(address, size);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, e);
+ }
+ }, true);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/android-34/android/os/HidlSupport.java b/android-34/android/os/HidlSupport.java
new file mode 100644
index 0000000..91b796a
--- /dev/null
+++ b/android-34/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-34/android/os/HwBinder.java b/android-34/android/os/HwBinder.java
new file mode 100644
index 0000000..feed208
--- /dev/null
+++ b/android-34/android/os/HwBinder.java
@@ -0,0 +1,166 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.NoSuchElementException;
+
+/** @hide */
+@SystemApi
+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;
+
+ /**
+ * This allows getService to bypass the VINTF manifest for testing only.
+ *
+ * Disabled on user builds.
+ * @hide
+ */
+ public static native final void setTrebleTestingOverride(
+ boolean testingOverride);
+
+ /**
+ * 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(maxTargetSdk = 30, trackingBug = 170729553)
+ public static void reportSyspropChanged() {
+ native_report_sysprop_change();
+ }
+}
diff --git a/android-34/android/os/HwBlob.java b/android-34/android/os/HwBlob.java
new file mode 100644
index 0000000..a43fbdb
--- /dev/null
+++ b/android-34/android/os/HwBlob.java
@@ -0,0 +1,461 @@
+/*
+ * 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 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
+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);
+ /**
+ * For embedded fields that follow a two-step approach for reading, first obtain their field
+ * handle using this method, and pass that field handle to the respective
+ * HwParcel.readEmbedded*() method.
+ * @param offset The field offset.
+ * @return The field handle.
+ */
+ public native final long getFieldHandle(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);
+
+ /**
+ * Writes a HidlMemory instance (without duplicating the underlying file descriptors) at an
+ * offset.
+ *
+ * @param offset location to write value
+ * @param mem a {@link HidlMemory} instance to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jobject)] is out of range
+ */
+ public final void putHidlMemory(long offset, @NonNull HidlMemory mem) {
+ putNativeHandle(offset + 0 /* offset of 'handle' field. */, mem.getHandle());
+ putInt64(offset + 16 /* offset of 'size' field. */, mem.getSize());
+ putString(offset + 24 /* offset of 'name' field. */, mem.getName());
+ }
+
+ /**
+ * @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-34/android/os/HwParcel.java b/android-34/android/os/HwParcel.java
new file mode 100644
index 0000000..9fd37d4
--- /dev/null
+++ b/android-34/android/os/HwParcel.java
@@ -0,0 +1,703 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/** @hide */
+@SystemApi
+public class HwParcel {
+ private static final String TAG = "HwParcel";
+
+ /** @hide */
+ @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 right type of interface.
+ *
+ * @param interfaceName fully qualified name of interface message
+ * is being sent to.
+ */
+ @FastNative
+ public native final void writeInterfaceToken(String interfaceName);
+ /**
+ * Writes a boolean value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ public native final void writeBool(boolean val);
+ /**
+ * Writes a byte value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ public native final void writeInt8(byte val);
+ /**
+ * Writes a short value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ public native final void writeInt16(short val);
+ /**
+ * Writes a int value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ public native final void writeInt32(int val);
+ /**
+ * Writes a long value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ public native final void writeInt64(long val);
+ /**
+ * Writes a float value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ public native final void writeFloat(float val);
+ /**
+ * Writes a double value to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ public native final void writeNativeHandle(@Nullable NativeHandle val);
+
+ /**
+ * Writes an array of boolean values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ private native final void writeBoolVector(boolean[] val);
+ /**
+ * Writes an array of byte values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ private native final void writeInt8Vector(byte[] val);
+ /**
+ * Writes an array of short values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ private native final void writeInt16Vector(short[] val);
+ /**
+ * Writes an array of int values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ private native final void writeInt32Vector(int[] val);
+ /**
+ * Writes an array of long values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ private native final void writeInt64Vector(long[] val);
+ /**
+ * Writes an array of float values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ private native final void writeFloatVector(float[] val);
+ /**
+ * Writes an array of double values to the end of the parcel.
+ * @param val to write
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ public native final void writeStrongBinder(IHwBinder binder);
+
+ /**
+ * Write a HidlMemory object (without duplicating the underlying file descriptors) to the end
+ * of the parcel.
+ *
+ * @param memory value to write
+ */
+ @FastNative
+ public native final void writeHidlMemory(@NonNull HidlMemory memory);
+
+ /**
+ * 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ public native final IHwBinder readStrongBinder();
+
+ /**
+ * Reads a HidlMemory value (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 HidlMemory#dup()}, which makes you also
+ * responsible for calling {@link HidlMemory#close()}.
+ *
+ * @return HidlMemory object read from parcel.
+ * @throws IllegalArgumentException if the parcel has no more data or is otherwise corrupt.
+ */
+ @FastNative
+ @NonNull
+ public native final HidlMemory readHidlMemory();
+
+ /**
+ * Reads an embedded HidlMemory (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 HidlMemory#dup()}. You
+ * do not need to call close on the HidlMemory returned from this.
+ *
+ * @param fieldHandle handle of the field, obtained from the {@link HwBlob}.
+ * @param parentHandle parentHandle from which to read the embedded object
+ * @param offset offset into parent
+ * @return a {@link HidlMemory} instance parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
+ @FastNative
+ @NonNull
+ public native final @Nullable
+ HidlMemory readEmbeddedHidlMemory(long fieldHandle, long parentHandle, long offset);
+
+ /**
+ * Read opaque segment of data as a blob.
+ * @return blob of size expectedSize
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ 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.
+ */
+ @FastNative
+ public native final void writeBuffer(HwBlob blob);
+ /**
+ * Write a status value into the blob.
+ * @param status value to write
+ */
+ @FastNative
+ 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
+ */
+ @FastNative
+ public native final void verifySuccess();
+ /**
+ * Should be called to reduce memory pressure when this object no longer needs
+ * to be written to.
+ */
+ @FastNative
+ 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.
+ */
+ @FastNative
+ 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();
+
+ @FastNative
+ 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-34/android/os/HwRemoteBinder.java b/android-34/android/os/HwRemoteBinder.java
new file mode 100644
index 0000000..756202e
--- /dev/null
+++ b/android-34/android/os/HwRemoteBinder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.compat.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-34/android/os/IBinder.java b/android-34/android/os/IBinder.java
new file mode 100644
index 0000000..90e4b17
--- /dev/null
+++ b/android-34/android/os/IBinder.java
@@ -0,0 +1,364 @@
+/*
+ * 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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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;
+
+ /**
+ * Flag to {@link #transact}: request binder driver to clear transaction data.
+ *
+ * Be very careful when using this flag in Java, since Java objects read from a Java
+ * Parcel may be non-trivial to clear.
+ * @hide
+ */
+ int FLAG_CLEAR_BUF = 0x00000020;
+
+ /**
+ * @hide
+ */
+ int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002;
+
+ /**
+ * 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;
+
+ /**
+ * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
+ * buffer limit.
+ */
+ static int getSuggestedMaxIpcSizeBytes() {
+ return MAX_IPC_SIZE;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * Get the binder extension of this binder interface.
+ * This allows one to customize an interface without having to modify the original interface.
+ *
+ * @return null if don't have binder extension
+ * @throws RemoteException
+ * @hide
+ */
+ public default @Nullable IBinder getExtension() throws RemoteException {
+ throw new IllegalStateException("Method is not implemented");
+ }
+
+ /**
+ * 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. For a oneway call to a different process false should never be
+ * returned. If a oneway call is made to code in the same process (usually to
+ * a C++ or Rust implementation), then there are no oneway semantics, and
+ * false can still be returned.
+ */
+ 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();
+
+ /**
+ * Interface for receiving a callback when the process hosting an IBinder
+ * has gone away.
+ * @param who The IBinder that has become invalid
+ */
+ default void binderDied(@NonNull IBinder who) {
+ 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>This will automatically be unlinked when all references to the linked
+ * binder proxy are dropped.</p>
+ *
+ * <p>You will only receive death notifications for remote binders,
+ * as local binders by definition can't die without you dying as well.</p>
+ *
+ * @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-34/android/os/IHwBinder.java b/android-34/android/os/IHwBinder.java
new file mode 100644
index 0000000..249eb3a
--- /dev/null
+++ b/android-34/android/os/IHwBinder.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+/** @hide */
+@SystemApi
+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-34/android/os/IHwInterface.java b/android-34/android/os/IHwInterface.java
new file mode 100644
index 0000000..f21f6e3
--- /dev/null
+++ b/android-34/android/os/IHwInterface.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/** @hide */
+@SystemApi
+public interface IHwInterface {
+ /**
+ * @return the binder object that corresponds to this interface.
+ */
+ public IHwBinder asBinder();
+}
diff --git a/android-34/android/os/IInterface.java b/android-34/android/os/IInterface.java
new file mode 100644
index 0000000..2a2605a
--- /dev/null
+++ b/android-34/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-34/android/os/IncidentManager.java b/android-34/android/os/IncidentManager.java
new file mode 100644
index 0000000..b97993a
--- /dev/null
+++ b/android-34/android/os/IncidentManager.java
@@ -0,0 +1,804 @@
+/*
+ * 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.content.Context;
+import android.net.Uri;
+import android.util.Slog;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Class to take an incident report.
+ *
+ * @hide
+ */
+@SystemApi
+@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 whether corresponding pending report allows consentless bugreport.
+ */
+ public static final int FLAG_ALLOW_CONSENTLESS_BUGREPORT = 0x2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_CONFIRMATION_DIALOG,
+ FLAG_ALLOW_CONSENTLESS_BUGREPORT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PendingReportFlags {}
+
+ /**
+ * 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
+ 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 PendingReportFlags
+ */
+ @PendingReportFlags
+ 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(@Nullable 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
+ 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() {
+ }
+ }
+
+ /**
+ * Callback for dumping an extended (usually vendor-supplied) incident report section
+ *
+ * @see #registerSection
+ * @see #unregisterSection
+ */
+ public static class DumpCallback {
+ private int mId;
+ private Executor mExecutor;
+
+ IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+ @Override
+ public void onDumpSection(ParcelFileDescriptor pfd) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ DumpCallback.this.onDumpSection(mId,
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ });
+ } else {
+ DumpCallback.this.onDumpSection(mId,
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ }
+ }
+ };
+
+ /**
+ * Dump the registered section as a protobuf message to the given OutputStream. Called when
+ * incidentd requests to dump this section.
+ *
+ * @param id the id of the registered section. The same id used in calling
+ * {@link #registerSection(int, String, DumpCallback)} will be passed in here.
+ * @param out the OutputStream to write the protobuf message
+ */
+ public void onDumpSection(int id, @NonNull OutputStream out) {
+ }
+ }
+
+ /**
+ * @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);
+ }
+ }
+
+ /**
+ * Register a callback to dump an extended incident report section with the given id and name,
+ * running on the supplied executor.
+ *
+ * Calling <code>registerSection</code> with a duplicate id will override previous registration.
+ * However, the request must come from the same calling uid.
+ *
+ * @param id the ID of the extended section. It should be unique system-wide, and be
+ * different from IDs of all existing section in
+ * frameworks/base/core/proto/android/os/incident.proto.
+ * Also see incident.proto for other rules about the ID.
+ * @param name the name to display in logs and/or stderr when taking an incident report
+ * containing this section, mainly for debugging purpose
+ * @param executor the executor used to run the callback
+ * @param callback the callback function to be invoked when an incident report with all sections
+ * or sections matching the given id is being taken
+ */
+ public void registerSection(int id, @NonNull String name,
+ @NonNull @CallbackExecutor Executor executor, @NonNull DumpCallback callback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ try {
+ if (callback.mExecutor != null) {
+ throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+ + " registerSection");
+ }
+ callback.mExecutor = executor;
+ callback.mId = id;
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "registerSection can't find incident binder service");
+ return;
+ }
+ service.registerSection(id, name, callback.mBinder);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "registerSection failed", ex);
+ }
+ }
+
+ /**
+ * Unregister an extended section dump function. The section must be previously registered with
+ * {@link #registerSection(int, String, DumpCallback)} by the same calling uid.
+ */
+ public void unregisterSection(int id) {
+ try {
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "unregisterSection can't find incident binder service");
+ return;
+ }
+ service.unregisterSection(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unregisterSection failed", 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-34/android/os/IncidentReportArgs.java b/android-34/android/os/IncidentReportArgs.java
new file mode 100644
index 0000000..73e4914
--- /dev/null
+++ b/android-34/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.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+
+/**
+ * The arguments for an incident report.
+ * {@hide}
+ */
+@SystemApi
+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.
+ */
+ @NonNull
+ @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-34/android/os/InputConstants.java b/android-34/android/os/InputConstants.java
new file mode 100644
index 0000000..c8fa065
--- /dev/null
+++ b/android-34/android/os/InputConstants.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 InputConstants {
+ public static final int DEFAULT_DISPATCHING_TIMEOUT_MILLIS =
+ IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
+ * Build.HW_TIMEOUT_MULTIPLIER;
+}
diff --git a/android-34/android/os/IpcDataCache.java b/android-34/android/os/IpcDataCache.java
new file mode 100644
index 0000000..bf44d65
--- /dev/null
+++ b/android-34/android/os/IpcDataCache.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.StringDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.PropertyInvalidatedCache;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastPrintWriter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing,
+ * but doesn't hold a lock across data fetches on query misses.
+ *
+ * The intended use case is caching frequently-read, seldom-changed information normally retrieved
+ * across interprocess communication. Imagine that you've written a user birthday information
+ * daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface over
+ * binder. That binder interface looks something like this:
+ *
+ * <pre>
+ * parcelable Birthday {
+ * int month;
+ * int day;
+ * }
+ * interface IUserBirthdayService {
+ * Birthday getUserBirthday(int userId);
+ * }
+ * </pre>
+ *
+ * Suppose the service implementation itself looks like this...
+ *
+ * <pre>
+ * public class UserBirthdayServiceImpl implements IUserBirthdayService {
+ * private final HashMap<Integer, Birthday%> mUidToBirthday;
+ * {@literal @}Override
+ * public synchronized Birthday getUserBirthday(int userId) {
+ * return mUidToBirthday.get(userId);
+ * }
+ * private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) {
+ * mUidToBirthday.clear();
+ * mUidToBirthday.putAll(uidToBirthday);
+ * }
+ * }
+ * </pre>
+ *
+ * ... and we have a client in frameworks (loaded into every app process) that looks like this:
+ *
+ * <pre>
+ * public class ActivityThread {
+ * ...
+ * public Birthday getUserBirthday(int userId) {
+ * return GetService("birthdayd").getUserBirthday(userId);
+ * }
+ * ...
+ * }
+ * </pre>
+ *
+ * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call to
+ * the birthdayd process and consult its database of birthdays. If we query user birthdays
+ * frequently, we do a lot of work that we don't have to do, since user birthdays change
+ * infrequently.
+ *
+ * IpcDataCache is part of a pattern for optimizing this kind of information-querying code. Using
+ * {@code IpcDataCache}, you'd write the client this way:
+ *
+ * <pre>
+ * public class ActivityThread {
+ * ...
+ * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
+ * new IpcDataCache.QueryHandler<Integer, Birthday>() {
+ * {@literal @}Override
+ * public Birthday apply(Integer) {
+ * return GetService("birthdayd").getUserBirthday(userId);
+ * }
+ * };
+ * private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache
+ * private static final String BDAY_API = "getUserBirthday";
+ * private final IpcDataCache<Integer, Birthday%> mBirthdayCache = new
+ * IpcDataCache<Integer, Birthday%>(
+ * BDAY_CACHE_MAX, MODULE_SYSTEM, BDAY_API, BDAY_API, mBirthdayQuery);
+ *
+ * public void disableUserBirthdayCache() {
+ * mBirthdayCache.disableForCurrentProcess();
+ * }
+ * public void invalidateUserBirthdayCache() {
+ * mBirthdayCache.invalidateCache();
+ * }
+ * public Birthday getUserBirthday(int userId) {
+ * return mBirthdayCache.query(userId);
+ * }
+ * ...
+ * }
+ * </pre>
+ *
+ * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday
+ * for the first time; on subsequent queries, we return the already-known Birthday object.
+ *
+ * The second parameter to the IpcDataCache constructor is a string that identifies the "module"
+ * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any
+ * string is permitted. The third parameters is the name of the API being cached; this, too, can
+ * any value. The fourth is the name of the cache. The cache is usually named after th API.
+ * Some things you must know about the three strings:
+ * <list>
+ * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}.
+ * Usually, the SELinux rules permit a process to write a system property (and therefore
+ * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that
+ * although the cache can be constructed with any module string, whatever string is chosen must be
+ * consistent with the SELinux configuration.
+ * <ul> The API name can be any string of alphanumeric characters. All caches with the same API
+ * are invalidated at the same time. If a server supports several caches and all are invalidated
+ * in common, then it is most efficient to assign the same API string to every cache.
+ * <ul> The cache name can be any string. In debug output, the name is used to distiguish between
+ * caches with the same API name. The cache name is also used when disabling caches in the
+ * current process. So, invalidation is based on the module+api but disabling (which is generally
+ * a once-per-process operation) is based on the cache name.
+ * </list>
+ *
+ * User birthdays do occasionally change, so we have to modify the server to invalidate this
+ * cache when necessary. That invalidation code looks like this:
+ *
+ * <pre>
+ * public class UserBirthdayServiceImpl {
+ * ...
+ * public UserBirthdayServiceImpl() {
+ * ...
+ * ActivityThread.currentActivityThread().disableUserBirthdayCache();
+ * ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
+ * }
+ *
+ * private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) {
+ * mUidToBirthday.clear();
+ * mUidToBirthday.putAll(uidToBirthday);
+ * ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
+ * }
+ * ...
+ * }
+ * </pre>
+ *
+ * The call to {@code IpcDataCache.invalidateCache()} guarantees that all clients will re-fetch
+ * birthdays from binder during consequent calls to
+ * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock
+ * held, we maintain consistency between different client views of the birthday state. The use of
+ * IpcDataCache in this idiomatic way introduces no new race conditions.
+ *
+ * IpcDataCache has a few other features for doing things like incremental enhancement of cached
+ * values and invalidation of multiple caches (that all share the same property key) at once.
+ *
+ * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each
+ * time we update the cache. SELinux configuration must allow everyone to read this property
+ * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write
+ * the property. (These properties conventionally begin with the "cache_key." prefix.)
+ *
+ * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so
+ * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In this
+ * local case, there's no IPC, so use of the cache is (depending on exact circumstance)
+ * unnecessary.
+ *
+ * There may be queries for which it is more efficient to bypass the cache than to cache the
+ * result. This would be true, for example, if some queries would require frequent cache
+ * invalidation while other queries require infrequent invalidation. To expand on the birthday
+ * example, suppose that there is a userId that signifies "the next birthday". When passed this
+ * userId, the server returns the next birthday among all users - this value changes as time
+ * advances. The userId value can be cached, but the cache must be invalidated whenever a
+ * birthday occurs, and this invalidates all birthdays. If there is a large number of users,
+ * invalidation will happen so often that the cache provides no value.
+ *
+ * The class provides a bypass mechanism to handle this situation.
+ * <pre>
+ * public class ActivityThread {
+ * ...
+ * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
+ * new IpcDataCache.QueryHandler<Integer, Birthday>() {
+ * {@literal @}Override
+ * public Birthday apply(Integer) {
+ * return GetService("birthdayd").getUserBirthday(userId);
+ * }
+ * {@literal @}Override
+ * public boolean shouldBypassQuery(Integer userId) {
+ * return userId == NEXT_BIRTHDAY;
+ * }
+ * };
+ * ...
+ * }
+ * </pre>
+ *
+ * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that
+ * particular query. The {@code shouldBypassQuery()} method is not abstract and the default
+ * implementation returns false.
+ *
+ * For security, there is a allowlist of processes that are allowed to invalidate a cache. The
+ * allowlist includes normal runtime processes but does not include test processes. Test
+ * processes must call {@code IpcDataCache.disableForTestMode()} to disable all cache activity in
+ * that process.
+ *
+ * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding.
+ *
+ * To test a binder cache, create one or more tests that exercise the binder method. This should
+ * be done twice: once with production code and once with a special image that sets {@code DEBUG}
+ * and {@code VERIFY} true. In the latter case, verify that no cache inconsistencies are
+ * reported. If a cache inconsistency is reported, however, it might be a false positive. This
+ * happens if the server side data can be read and written non-atomically with respect to cache
+ * invalidation.
+ *
+ * @param <Query> The class used to index cache entries: must be hashable and comparable
+ * @param <Result> The class holding cache entries; use a boxed primitive if possible
+ * @hide
+ */
+@TestApi
+@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, Result> {
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static abstract class QueryHandler<Q,R>
+ extends PropertyInvalidatedCache.QueryHandler<Q,R> {
+ /**
+ * Compute a result given a query. The semantics are those of Functor.
+ */
+ public abstract @Nullable R apply(@NonNull Q query);
+
+ /**
+ * Return true if a query should not use the cache. The default implementation
+ * always uses the cache.
+ */
+ public boolean shouldBypassCache(@NonNull Q query) {
+ return false;
+ }
+ };
+
+ /**
+ * The list of cache namespaces. Each namespace corresponds to an sepolicy domain. A
+ * namespace is owned by a single process, although a single process can have more
+ * than one namespace (system_server, as an example).
+ * @hide
+ */
+ @StringDef(
+ prefix = { "MODULE_"
+ },
+ value = {
+ MODULE_TEST,
+ MODULE_SYSTEM,
+ MODULE_BLUETOOTH,
+ MODULE_TELEPHONY
+ }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IpcDataCacheModule { }
+
+ /**
+ * The module used for unit tests and cts tests. It is expected that no process in
+ * the system has permissions to write properties with this module.
+ * @hide
+ */
+ @TestApi
+ public static final String MODULE_TEST = PropertyInvalidatedCache.MODULE_TEST;
+
+ /**
+ * The module used for system server/framework caches. This is not visible outside
+ * the system processes.
+ * @hide
+ */
+ @TestApi
+ public static final String MODULE_SYSTEM = PropertyInvalidatedCache.MODULE_SYSTEM;
+
+ /**
+ * The module used for bluetooth caches.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static final String MODULE_BLUETOOTH = PropertyInvalidatedCache.MODULE_BLUETOOTH;
+
+ /**
+ * Make a new property invalidated cache. The key is computed from the module and api
+ * parameters.
+ *
+ * @param maxEntries Maximum number of entries to cache; LRU discard
+ * @param module The module under which the cache key should be placed.
+ * @param api The api this cache front-ends. The api must be a Java identifier but
+ * need not be an actual api.
+ * @param cacheName Name of this cache in debug and dumpsys
+ * @param computer The code to compute values that are not in the cache.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module,
+ @NonNull String api, @NonNull String cacheName,
+ @NonNull QueryHandler<Query, Result> computer) {
+ super(maxEntries, module, api, cacheName, computer);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @Override
+ public void disableForCurrentProcess() {
+ super.disableForCurrentProcess();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static void disableForCurrentProcess(@NonNull String cacheName) {
+ PropertyInvalidatedCache.disableForCurrentProcess(cacheName);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @Override
+ public @Nullable Result query(@NonNull Query query) {
+ return super.query(query);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @Override
+ public void invalidateCache() {
+ super.invalidateCache();
+ }
+
+ /**
+ * Invalidate caches in all processes that are keyed for the module and api.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static void invalidateCache(@NonNull @IpcDataCacheModule String module,
+ @NonNull String api) {
+ PropertyInvalidatedCache.invalidateCache(module, api);
+ }
+
+ /**
+ * This is a convenience class that encapsulates configuration information for a
+ * cache. It may be supplied to the cache constructors in lieu of the other
+ * parameters. The class captures maximum entry count, the module, the key, and the
+ * api.
+ *
+ * There are three specific use cases supported by this class.
+ *
+ * 1. Instance-per-cache: create a static instance of this class using the same
+ * parameters as would have been given to IpcDataCache (or
+ * PropertyInvalidatedCache). This static instance provides a hook for the
+ * invalidateCache() and disableForLocalProcess() calls, which, generally, must
+ * also be static.
+ *
+ * 2. Short-hand for shared configuration parameters: create an instance of this class
+ * to capture the maximum number of entries and the module to be used by more than
+ * one cache in the class. Refer to this instance when creating new configs. Only
+ * the api and (optionally key) for the new cache must be supplied.
+ *
+ * 3. Tied caches: create a static instance of this class to capture the maximum
+ * number of entries, the module, and the key. Refer to this instance when
+ * creating a new config that differs in only the api. The new config can be
+ * created as part of the cache constructor. All caches that trace back to the
+ * root config share the same key and are invalidated by the invalidateCache()
+ * method of the root config. All caches that trace back to the root config can be
+ * disabled in the local process by the disableAllForCurrentProcess() method of the
+ * root config.
+ *
+ * @hide
+ */
+ public static class Config {
+ private final int mMaxEntries;
+ @IpcDataCacheModule
+ private final String mModule;
+ private final String mApi;
+ private final String mName;
+
+ /**
+ * The list of cache names that were created extending this Config. If
+ * disableForCurrentProcess() is invoked on this config then all children will be
+ * disabled. Furthermore, any new children based off of this config will be
+ * disabled. The construction order guarantees that new caches will be disabled
+ * before they are created (the Config must be created before the IpcDataCache is
+ * created).
+ */
+ private ArraySet<String> mChildren;
+
+ /**
+ * True if registered children are disabled in the current process. If this is
+ * true then all new children are disabled as they are registered.
+ */
+ private boolean mDisabled = false;
+
+ public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
+ @NonNull String api, @NonNull String name) {
+ mMaxEntries = maxEntries;
+ mModule = module;
+ mApi = api;
+ mName = name;
+ }
+
+ /**
+ * A short-hand constructor that makes the name the same as the api.
+ */
+ public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
+ @NonNull String api) {
+ this(maxEntries, module, api, api);
+ }
+
+ /**
+ * Copy the module and max entries from the Config and take the api and name from
+ * the parameter list.
+ */
+ public Config(@NonNull Config root, @NonNull String api, @NonNull String name) {
+ this(root.maxEntries(), root.module(), api, name);
+ }
+
+ /**
+ * Copy the module and max entries from the Config and take the api and name from
+ * the parameter list.
+ */
+ public Config(@NonNull Config root, @NonNull String api) {
+ this(root.maxEntries(), root.module(), api, api);
+ }
+
+ /**
+ * Fetch a config that is a child of <this>. The child shares the same api as the
+ * parent and is registered with the parent for the purposes of disabling in the
+ * current process.
+ */
+ public Config child(@NonNull String name) {
+ final Config result = new Config(this, api(), name);
+ registerChild(name);
+ return result;
+ }
+
+ public final int maxEntries() {
+ return mMaxEntries;
+ }
+
+ @IpcDataCacheModule
+ public final @NonNull String module() {
+ return mModule;
+ }
+
+ public final @NonNull String api() {
+ return mApi;
+ }
+
+ public final @NonNull String name() {
+ return mName;
+ }
+
+ /**
+ * Register a child cache name. If disableForCurrentProcess() has been called
+ * against this cache, disable th new child.
+ */
+ private final void registerChild(String name) {
+ synchronized (this) {
+ if (mChildren == null) {
+ mChildren = new ArraySet<>();
+ }
+ mChildren.add(name);
+ if (mDisabled) {
+ IpcDataCache.disableForCurrentProcess(name);
+ }
+ }
+ }
+
+ /**
+ * Invalidate all caches that share this Config's module and api.
+ */
+ public void invalidateCache() {
+ IpcDataCache.invalidateCache(mModule, mApi);
+ }
+
+ /**
+ * Disable all caches that share this Config's name.
+ */
+ public void disableForCurrentProcess() {
+ IpcDataCache.disableForCurrentProcess(mName);
+ }
+
+ /**
+ * Disable this cache and all children. Any child that is added in the future
+ * will alwo be disabled.
+ */
+ public void disableAllForCurrentProcess() {
+ synchronized (this) {
+ mDisabled = true;
+ disableForCurrentProcess();
+ if (mChildren != null) {
+ for (String c : mChildren) {
+ IpcDataCache.disableForCurrentProcess(c);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a new cache using a config.
+ * @hide
+ */
+ public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
+ super(config.maxEntries(), config.module(), config.api(), config.name(), computer);
+ }
+
+ /**
+ * An interface suitable for a lambda expression instead of a QueryHandler.
+ * @hide
+ */
+ public interface RemoteCall<Query, Result> {
+ Result apply(Query query) throws RemoteException;
+ }
+
+ /**
+ * This is a query handler that is created with a lambda expression that is invoked
+ * every time the handler is called. The handler is specifically meant for services
+ * hosted by system_server; the handler automatically rethrows RemoteException as a
+ * RuntimeException, which is the usual handling for failed binder calls.
+ */
+ private static class SystemServerCallHandler<Query, Result>
+ extends IpcDataCache.QueryHandler<Query, Result> {
+ private final RemoteCall<Query, Result> mHandler;
+ public SystemServerCallHandler(RemoteCall handler) {
+ mHandler = handler;
+ }
+ @Override
+ public Result apply(Query query) {
+ try {
+ return mHandler.apply(query);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Create a cache using a config and a lambda expression.
+ * @hide
+ */
+ public IpcDataCache(@NonNull Config config, @NonNull RemoteCall<Query, Result> computer) {
+ this(config, new SystemServerCallHandler<>(computer));
+ }
+}
diff --git a/android-34/android/os/KernelCpuThreadReaderPerfTest.java b/android-34/android/os/KernelCpuThreadReaderPerfTest.java
new file mode 100644
index 0000000..da9ed6e
--- /dev/null
+++ b/android-34/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-34/android/os/LimitExceededException.java b/android-34/android/os/LimitExceededException.java
new file mode 100644
index 0000000..d934326
--- /dev/null
+++ b/android-34/android/os/LimitExceededException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/** Indicates that the app has exceeded a limit set by the System. */
+public class LimitExceededException extends IllegalStateException {
+
+ /**
+ * Constructs a new {@code LimitExceededException} with {@code null} as its
+ * detail message. The cause is not initialized, and may subsequently be
+ * initialized by a call to {@link #initCause}.
+ */
+ public LimitExceededException() {
+ super();
+ }
+
+ /**
+ * Constructs a new {@code LimitExceededException} with the specified detail message.
+ * The cause is not initialized, and may subsequently be initialized by a
+ * call to {@link #initCause}.
+ *
+ * @param message the detail message which is saved for later retrieval
+ * by the {@link #getMessage()} method.
+ */
+ public LimitExceededException(@NonNull String message) {
+ super(message);
+ }
+}
diff --git a/android-34/android/os/LocaleList.java b/android-34/android/os/LocaleList.java
new file mode 100644
index 0000000..b74bb33
--- /dev/null
+++ b/android-34/android/os/LocaleList.java
@@ -0,0 +1,580 @@
+/*
+ * 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.SuppressLint;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.icu.util.ULocale;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+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(@Nullable 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.writeString8(mStringRepresentation);
+ }
+
+ /**
+ * Retrieves a String representation of the language tags in this list.
+ */
+ @NonNull
+ public String toLanguageTags() {
+ return mStringRepresentation;
+ }
+
+ /**
+ * Creates a new {@link LocaleList}.
+ *
+ * If two or more same locales are passed, the repeated locales will be dropped.
+ * <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>.
+ */
+ public LocaleList(@NonNull Locale... list) {
+ if (list.length == 0) {
+ mList = sEmptyList;
+ mStringRepresentation = "";
+ } else {
+ final ArrayList<Locale> localeList = new ArrayList<>();
+ 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)) {
+ // Dropping duplicated locale entries.
+ } else {
+ final Locale localeClone = (Locale) l.clone();
+ localeList.add(localeClone);
+ sb.append(localeClone.toLanguageTag());
+ if (i < list.length - 1) {
+ sb.append(',');
+ }
+ seenLocales.add(localeClone);
+ }
+ }
+ mList = localeList.toArray(new Locale[localeList.size()]);
+ 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.readString8());
+ }
+
+ @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);
+ }
+
+ /**
+ * Determine whether two locales are considered a match, even if they are not exactly equal.
+ * They are considered as a match when both of their languages and scripts
+ * (explicit or inferred) are identical. This means that a user would be able to understand
+ * the content written in the supported locale even if they say they prefer the desired locale.
+ *
+ * E.g. [zh-HK] matches [zh-Hant]; [en-US] matches [en-CA]
+ *
+ * @param supported The supported {@link Locale} to be compared.
+ * @param desired The desired {@link Locale} to be compared.
+ * @return True if they match, false otherwise.
+ */
+ public static boolean matchesLanguageAndScript(@SuppressLint("UseIcu") @NonNull
+ Locale supported, @SuppressLint("UseIcu") @NonNull Locale desired) {
+ if (supported.equals(desired)) {
+ return true; // return early so we don't do unnecessary computation
+ }
+ if (!supported.getLanguage().equals(desired.getLanguage())) {
+ return false;
+ }
+ 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 false;
+ }
+ 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());
+ }
+ 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);
+ }
+
+ private int findFirstMatchIndex(Locale supportedLocale) {
+ for (int idx = 0; idx < mList.length; idx++) {
+ if (matchesLanguageAndScript(supportedLocale, mList[idx])) {
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/LongArrayMultiStateCounterPerfTest.java b/android-34/android/os/LongArrayMultiStateCounterPerfTest.java
new file mode 100644
index 0000000..8d2d044
--- /dev/null
+++ b/android-34/android/os/LongArrayMultiStateCounterPerfTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.LongArrayMultiStateCounter;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LongArrayMultiStateCounterPerfTest {
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * A complete line-for-line reimplementation of
+ * {@link com.android.internal.os.LongArrayMultiStateCounter}, only in Java instead of
+ * native.
+ */
+ private static class TestLongArrayMultiStateCounter {
+ private final int mStateCount;
+ private final int mArrayLength;
+ private int mCurrentState;
+ private long mLastStateChangeTimestampMs = -1;
+ private long mLastUpdateTimestampMs = -1;
+
+ private static class State {
+ private long mTimeInStateSinceUpdate;
+ private long[] mCounter;
+ }
+
+ private final State[] mStates;
+ private final long[] mLastTimeInFreq;
+ private final long[] mDelta;
+
+ TestLongArrayMultiStateCounter(int stateCount, int arrayLength) {
+ mStateCount = stateCount;
+ mArrayLength = arrayLength;
+ mStates = new State[stateCount];
+ for (int i = 0; i < mStateCount; i++) {
+ mStates[i] = new State();
+ mStates[i].mCounter = new long[mArrayLength];
+ }
+ mLastTimeInFreq = new long[mArrayLength];
+ mDelta = new long[mArrayLength];
+ }
+
+ public void setState(int state, long timestampMs) {
+ if (mLastStateChangeTimestampMs > 0) {
+ if (timestampMs >= mLastStateChangeTimestampMs) {
+ mStates[mCurrentState].mTimeInStateSinceUpdate +=
+ timestampMs - mLastStateChangeTimestampMs;
+ } else {
+ for (int i = 0; i < mStateCount; i++) {
+ mStates[i].mTimeInStateSinceUpdate = 0;
+ }
+ }
+ }
+ mCurrentState = state;
+ mLastStateChangeTimestampMs = timestampMs;
+ }
+
+ public void updateValue(long[] timeInFreq, long timestampMs) {
+ setState(mCurrentState, timestampMs);
+
+ if (mLastUpdateTimestampMs >= 0) {
+ if (timestampMs > mLastUpdateTimestampMs) {
+ if (delta(mLastTimeInFreq, timeInFreq, mDelta)) {
+ long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
+ for (int i = 0; i < mStateCount; i++) {
+ long timeInState = mStates[i].mTimeInStateSinceUpdate;
+ if (timeInState > 0) {
+ add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate);
+ mStates[i].mTimeInStateSinceUpdate = 0;
+ }
+ }
+ } else {
+ throw new RuntimeException();
+ }
+ } else if (timestampMs < mLastUpdateTimestampMs) {
+ throw new RuntimeException();
+ }
+ }
+ System.arraycopy(timeInFreq, 0, mLastTimeInFreq, 0, mArrayLength);
+ mLastUpdateTimestampMs = timestampMs;
+ }
+
+ private boolean delta(long[] timeInFreq1, long[] timeInFreq2, long[] delta) {
+ if (delta.length != mArrayLength) {
+ throw new RuntimeException();
+ }
+
+ boolean is_delta_valid = true;
+ for (int i = 0; i < mStateCount; i++) {
+ if (timeInFreq2[i] >= timeInFreq1[i]) {
+ delta[i] = timeInFreq2[i] - timeInFreq1[i];
+ } else {
+ delta[i] = 0;
+ is_delta_valid = false;
+ }
+ }
+
+ return is_delta_valid;
+ }
+
+ private void add(long[] counter, long[] delta, long numerator, long denominator) {
+ if (numerator != denominator) {
+ for (int i = 0; i < mArrayLength; i++) {
+ counter[i] += delta[i] * numerator / denominator;
+ }
+ } else {
+ for (int i = 0; i < mArrayLength; i++) {
+ counter[i] += delta[i];
+ }
+ }
+ }
+ }
+
+ @Test
+ public void javaImplementation() {
+ TestLongArrayMultiStateCounter counter =
+ new TestLongArrayMultiStateCounter(2, 4);
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long time = 1000;
+ long[] timeInFreq = {100, 200, 300, 400};
+ while (state.keepRunning()) {
+ counter.setState(1, time);
+ counter.setState(0, time + 1000);
+ counter.updateValue(timeInFreq, time + 2000);
+ time += 10000;
+ }
+ }
+
+ @Test
+ public void nativeImplementation() {
+ LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long time = 1000;
+ LongArrayMultiStateCounter.LongArrayContainer timeInFreq =
+ new LongArrayMultiStateCounter.LongArrayContainer(4);
+ timeInFreq.setValues(new long[]{100, 200, 300, 400});
+ while (state.keepRunning()) {
+ counter.setState(1, time);
+ counter.setState(0, time + 1000);
+ counter.updateValues(timeInFreq, time + 2000);
+ time += 10000;
+ }
+ }
+}
diff --git a/android-34/android/os/Looper.java b/android-34/android/os/Looper.java
new file mode 100644
index 0000000..712d328
--- /dev/null
+++ b/android-34/android/os/Looper.java
@@ -0,0 +1,499 @@
+/*
+ * 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.compat.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(Looper.myLooper()) {
+ * 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;
+ private boolean mInLoop;
+
+ @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;
+
+ /**
+ * True if a message delivery takes longer than {@link #mSlowDeliveryThresholdMs}.
+ */
+ private boolean mSlowDeliveryDetected;
+
+ /** 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. See also: {@link #prepare()}
+ *
+ * @deprecated The main looper for your application is created by the Android environment,
+ * so you should never need to call this function yourself.
+ */
+ @Deprecated
+ 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;
+ }
+
+ /**
+ * Poll and deliver single message, return true if the outer loop should continue.
+ */
+ @SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
+ "ClearIdentityCallNotFollowedByTryFinally"})
+ private static boolean loopOnce(final Looper me,
+ final long ident, final int thresholdOverride) {
+ Message msg = me.mQueue.next(); // might block
+ if (msg == null) {
+ // No message indicates that the message queue is quitting.
+ return false;
+ }
+
+ // 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;
+
+ final boolean hasOverride = thresholdOverride >= 0;
+ if (hasOverride) {
+ slowDispatchThresholdMs = thresholdOverride;
+ slowDeliveryThresholdMs = thresholdOverride;
+ }
+ final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
+ && (msg.when > 0);
+ final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);
+
+ 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 (me.mSlowDeliveryDetected) {
+ if ((dispatchStart - msg.when) <= 10) {
+ Slog.w(TAG, "Drained");
+ me.mSlowDeliveryDetected = false;
+ }
+ } else {
+ if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
+ msg)) {
+ // Once we write a slow delivery log, suppress until the queue drains.
+ me.mSlowDeliveryDetected = 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();
+
+ return true;
+ }
+
+ /**
+ * Run the message queue in this thread. Be sure to call
+ * {@link #quit()} to end the loop.
+ */
+ @SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
+ "ClearIdentityCallNotFollowedByTryFinally",
+ "ResultOfClearIdentityCallNotStoredInVariable"})
+ public static void loop() {
+ final Looper me = myLooper();
+ if (me == null) {
+ throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
+ }
+ if (me.mInLoop) {
+ Slog.w(TAG, "Loop again would have the queued messages be executed"
+ + " before this one completed.");
+ }
+
+ me.mInLoop = true;
+
+ // 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", -1);
+
+ me.mSlowDeliveryDetected = false;
+
+ for (;;) {
+ if (!loopOnce(me, ident, thresholdOverride)) {
+ return;
+ }
+ }
+ }
+
+ 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 dumpDebug(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.dumpDebug(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-34/android/os/LooperStatsPerfTest.java b/android-34/android/os/LooperStatsPerfTest.java
new file mode 100644
index 0000000..162167d
--- /dev/null
+++ b/android-34/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-34/android/os/MemoryFile.java b/android-34/android/os/MemoryFile.java
new file mode 100644
index 0000000..95337f6
--- /dev/null
+++ b/android-34/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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/Message.java b/android-34/android/os/Message.java
new file mode 100644
index 0000000..72fb4ae
--- /dev/null
+++ b/android-34/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.compat.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 dumpDebug(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(), java.lang.Object.class);
+ }
+ when = source.readLong();
+ data = source.readBundle();
+ replyTo = Messenger.readMessengerOrNullFromParcel(source);
+ sendingUid = source.readInt();
+ workSourceUid = source.readInt();
+ }
+}
diff --git a/android-34/android/os/MessageQueue.java b/android-34/android/os/MessageQueue.java
new file mode 100644
index 0000000..87c4f33
--- /dev/null
+++ b/android-34/android/os/MessageQueue.java
@@ -0,0 +1,1044 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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
+ */
+ @UnsupportedAppUsage
+ @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
+ */
+ @UnsupportedAppUsage
+ @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.");
+ }
+
+ synchronized (this) {
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
+ }
+
+ 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;
+ }
+ }
+
+ boolean hasEqualMessages(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 || object.equals(p.obj))) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 removeEqualMessages(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 || object.equals(p.obj))) {
+ 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 || object.equals(n.obj))) {
+ 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 removeEqualMessages(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 || object.equals(p.obj))) {
+ 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 || object.equals(n.obj))) {
+ 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;
+ }
+ }
+ }
+
+ void removeCallbacksAndEqualMessages(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 || object.equals(p.obj))) {
+ 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 || object.equals(n.obj))) {
+ 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 dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long messageQueueToken = proto.start(fieldId);
+ synchronized (this) {
+ for (Message msg = mMessages; msg != null; msg = msg.next) {
+ msg.dumpDebug(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-34/android/os/Messenger.java b/android-34/android/os/Messenger.java
new file mode 100644
index 0000000..ed851b3
--- /dev/null
+++ b/android-34/android/os/Messenger.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 android.annotation.Nullable;
+
+/**
+ * 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(@Nullable 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-34/android/os/NativeHandle.java b/android-34/android/os/NativeHandle.java
new file mode 100644
index 0000000..a26873a
--- /dev/null
+++ b/android-34/android/os/NativeHandle.java
@@ -0,0 +1,216 @@
+/*
+ * 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.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
+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-34/android/os/NetworkOnMainThreadException.java b/android-34/android/os/NetworkOnMainThreadException.java
new file mode 100644
index 0000000..dd8c66c
--- /dev/null
+++ b/android-34/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-34/android/os/NewUserRequest.java b/android-34/android/os/NewUserRequest.java
new file mode 100644
index 0000000..45ad74e
--- /dev/null
+++ b/android-34/android/os/NewUserRequest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+
+/**
+ * Contains necessary information to create user using
+ * {@link UserManager#createUser(NewUserRequest)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressLint("PackageLayering")
+public final class NewUserRequest {
+ @Nullable
+ private final String mName;
+ private final boolean mAdmin;
+ private final boolean mEphemeral;
+ @NonNull
+ private final String mUserType;
+ private final Bitmap mUserIcon;
+ private final String mAccountName;
+ private final String mAccountType;
+ private final PersistableBundle mAccountOptions;
+
+ private NewUserRequest(Builder builder) {
+ mName = builder.mName;
+ mAdmin = builder.mAdmin;
+ mEphemeral = builder.mEphemeral;
+ mUserType = builder.mUserType;
+ mUserIcon = builder.mUserIcon;
+ mAccountName = builder.mAccountName;
+ mAccountType = builder.mAccountType;
+ mAccountOptions = builder.mAccountOptions;
+ }
+
+ /**
+ * Returns the name of the user.
+ */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns whether the user is ephemeral.
+ *
+ * <p> Ephemeral user will be removed after leaving the foreground.
+ */
+ public boolean isEphemeral() {
+ return mEphemeral;
+ }
+
+ /**
+ * Returns whether the user is an admin.
+ *
+ * <p> Admin user is with administrative privileges and such user can create and
+ * delete users.
+ */
+ public boolean isAdmin() {
+ return mAdmin;
+ }
+
+ /**
+ * Returns the calculated flags for user creation.
+ */
+ int getFlags() {
+ int flags = 0;
+ if (isAdmin()) flags |= UserInfo.FLAG_ADMIN;
+ if (isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL;
+ return flags;
+ }
+
+ /**
+ * Returns the user type.
+ *
+ * <p> Default value is {@link UserManager#USER_TYPE_FULL_SECONDARY}
+ */
+ @NonNull
+ public String getUserType() {
+ return mUserType;
+ }
+
+ /**
+ * Returns the user icon.
+ */
+ @Nullable
+ public Bitmap getUserIcon() {
+ return mUserIcon;
+ }
+
+ /**
+ * Returns the account name.
+ */
+ @Nullable
+ public String getAccountName() {
+ return mAccountName;
+ }
+
+ /**
+ * Returns the account type.
+ */
+ @Nullable
+ public String getAccountType() {
+ return mAccountType;
+ }
+
+ /**
+ * Returns the account options.
+ */
+ @SuppressLint("NullableCollection")
+ @Nullable
+ public PersistableBundle getAccountOptions() {
+ return mAccountOptions;
+ }
+
+ @Override
+ public String toString() {
+ return "NewUserRequest{"
+ + "mName='" + mName + '\''
+ + ", mAdmin=" + mAdmin
+ + ", mEphemeral=" + mEphemeral
+ + ", mUserType='" + mUserType + '\''
+ + ", mAccountName='" + mAccountName + '\''
+ + ", mAccountType='" + mAccountType + '\''
+ + ", mAccountOptions=" + mAccountOptions
+ + '}';
+ }
+
+ /**
+ * Builder for building {@link NewUserRequest}
+ */
+ @SuppressLint("PackageLayering")
+ public static final class Builder {
+
+ private String mName;
+ private boolean mAdmin;
+ private boolean mEphemeral;
+ private String mUserType = UserManager.USER_TYPE_FULL_SECONDARY;
+ private Bitmap mUserIcon;
+ private String mAccountName;
+ private String mAccountType;
+ private PersistableBundle mAccountOptions;
+
+ /**
+ * Sets user name.
+ *
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setName(@Nullable String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Sets user as admin.
+ *
+ * <p> Admin user is with administrative privileges and such user can create
+ * and delete users.
+ *
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setAdmin() {
+ mAdmin = true;
+ return this;
+ }
+
+ /**
+ * Sets user as ephemeral.
+ *
+ * <p> Ephemeral user will be removed after leaving the foreground.
+ *
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setEphemeral() {
+ mEphemeral = true;
+ return this;
+ }
+
+ /**
+ * Sets user type.
+ *
+ * <p> Default value is {link UserManager#USER_TYPE_FULL_SECONDARY}.
+ *
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setUserType(@NonNull String type) {
+ mUserType = type;
+ return this;
+ }
+
+ /**
+ * Sets user icon.
+ *
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setUserIcon(@Nullable Bitmap userIcon) {
+ mUserIcon = userIcon;
+ return this;
+ }
+
+ /**
+ * Sets account name that will be used by the setup wizard to initialize the user.
+ *
+ * @see android.accounts.Account
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setAccountName(@Nullable String accountName) {
+ mAccountName = accountName;
+ return this;
+ }
+
+ /**
+ * Sets account type for the account to be created. This is required if the account name
+ * is not null. This will be used by the setup wizard to initialize the user.
+ *
+ * @see android.accounts.Account
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setAccountType(@Nullable String accountType) {
+ mAccountType = accountType;
+ return this;
+ }
+
+ /**
+ * Sets account options that can contain account-specific extra information
+ * to be used by setup wizard to initialize the account for the user.
+ *
+ * @return This object for method chaining.
+ */
+ @NonNull
+ public Builder setAccountOptions(@Nullable PersistableBundle accountOptions) {
+ mAccountOptions = accountOptions;
+ return this;
+ }
+
+ /**
+ * Builds {@link NewUserRequest}
+ *
+ * @throws IllegalStateException if builder is configured with incompatible properties and
+ * it is not possible to create such user. For example - a guest admin user.
+ */
+ @NonNull
+ public NewUserRequest build() {
+ checkIfPropertiesAreCompatible();
+ return new NewUserRequest(this);
+ }
+
+ private void checkIfPropertiesAreCompatible() {
+ if (mUserType == null) {
+ throw new IllegalStateException("Usertype cannot be null");
+ }
+
+ // Admin user can only be USER_TYPE_FULL_SECONDARY
+ if (mAdmin && !mUserType.equals(UserManager.USER_TYPE_FULL_SECONDARY)) {
+ throw new IllegalStateException("Admin user can't be of type: " + mUserType);
+ }
+
+ if (TextUtils.isEmpty(mAccountName) != TextUtils.isEmpty(mAccountType)) {
+ throw new IllegalStateException(
+ "Account name and account type should be provided together.");
+ }
+ }
+ }
+}
diff --git a/android-34/android/os/NewUserResponse.java b/android-34/android/os/NewUserResponse.java
new file mode 100644
index 0000000..f48d9e5
--- /dev/null
+++ b/android-34/android/os/NewUserResponse.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+import android.annotation.TestApi;
+
+/**
+ * Contains the response of the call {@link UserManager#createUser(NewUserRequest)}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NewUserResponse {
+
+ private final @Nullable UserHandle mUser;
+ private final @UserManager.UserOperationResult int mOperationResult;
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public NewUserResponse(@Nullable UserHandle user,
+ @UserManager.UserOperationResult int operationResult) {
+ mUser = user;
+ mOperationResult = operationResult;
+ }
+
+ /**
+ * Is user creation successful?
+ */
+ public boolean isSuccessful() {
+ return mUser != null;
+ }
+
+ // TODO(b/199446283): If UserHandle.NULL is systemAPI, that can be returned here instead of null
+ /**
+ * Gets the created user handle.
+ */
+ public @Nullable UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Gets operation results.
+ */
+ public @UserManager.UserOperationResult int getOperationResult() {
+ return mOperationResult;
+ }
+
+ @Override
+ public String toString() {
+ return "NewUserResponse{"
+ + "mUser="
+ + mUser
+ + ", mOperationResult=" + mOperationResult
+ + '}';
+ }
+}
diff --git a/android-34/android/os/NullVibrator.java b/android-34/android/os/NullVibrator.java
new file mode 100644
index 0000000..7859b5c
--- /dev/null
+++ b/android-34/android/os/NullVibrator.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * 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 isVibrating() {
+ return false;
+ }
+
+ @Override
+ public boolean hasAmplitudeControl() {
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, VibrationEffect effect,
+ String reason, VibrationAttributes attributes) {
+ }
+
+ @Override
+ public void cancel() {
+ }
+
+ @Override
+ public void cancel(int usageFilter) {
+ }
+}
diff --git a/android-34/android/os/OperationCanceledException.java b/android-34/android/os/OperationCanceledException.java
new file mode 100644
index 0000000..b0cd663
--- /dev/null
+++ b/android-34/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-34/android/os/OutcomeReceiver.java b/android-34/android/os/OutcomeReceiver.java
new file mode 100644
index 0000000..4b9552e
--- /dev/null
+++ b/android-34/android/os/OutcomeReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Callback interface intended for use when an asynchronous operation may result in a failure.
+ *
+ * This interface may be used in cases where an asynchronous API may complete either with a value
+ * or with a {@link Throwable} that indicates an error.
+ * @param <R> The type of the result that's being sent.
+ * @param <E> The type of the {@link Throwable} that contains more information about the error.
+ */
+public interface OutcomeReceiver<R, E extends Throwable> {
+ /**
+ * Called when the asynchronous operation succeeds and delivers a result value.
+ * @param result The value delivered by the asynchronous operation.
+ */
+ void onResult(R result);
+
+ /**
+ * Called when the asynchronous operation fails. The mode of failure is indicated by the
+ * {@link Throwable} passed as an argument to this method.
+ * @param error A subclass of {@link Throwable} with more details about the error that occurred.
+ */
+ default void onError(@NonNull E error) {}
+}
diff --git a/android-34/android/os/PackageManagerPerfTest.java b/android-34/android/os/PackageManagerPerfTest.java
new file mode 100644
index 0000000..4bcc8c4
--- /dev/null
+++ b/android-34/android/os/PackageManagerPerfTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PackageManagerPerfTest {
+ private static final String PERMISSION_NAME_EXISTS =
+ "com.android.perftests.packagemanager.TestPermission";
+ private static final String PERMISSION_NAME_DOESNT_EXIST =
+ "com.android.perftests.packagemanager.TestBadPermission";
+ private static final ComponentName TEST_ACTIVITY =
+ new ComponentName("com.android.perftests.packagemanager",
+ "android.perftests.utils.PerfTestActivity");
+ private static final String TEST_FIELD = "test";
+ private static final String TEST_VALUE = "value";
+
+ @Rule
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Rule
+ public final PlatformCompatChangeRule mPlatformCompatChangeRule =
+ new PlatformCompatChangeRule();
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES);
+
+ final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private PackageInstaller mPackageInstaller;
+
+ public PackageManagerPerfTest() throws PackageManager.NameNotFoundException {
+ mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+ }
+
+ private void installTestApp(TestApp testApp) throws IOException, InterruptedException {
+ Install install = Install.single(testApp);
+ final int expectedSessionId = install.createSession();
+ PackageInstaller.Session session =
+ InstallUtils.openPackageInstallerSession(expectedSessionId);
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(TEST_FIELD, TEST_VALUE);
+ session.setAppMetadata(bundle);
+ LocalIntentSender localIntentSender = new LocalIntentSender();
+ session.commit(localIntentSender.getIntentSender());
+ Intent intent = localIntentSender.getResult();
+ InstallUtils.assertStatusSuccess(intent);
+ }
+
+ private void uninstallTestApp(String packageName) throws InterruptedException {
+ LocalIntentSender localIntentSender = new LocalIntentSender();
+ IntentSender intentSender = localIntentSender.getIntentSender();
+ mPackageInstaller.uninstall(packageName, intentSender);
+ Intent intent = localIntentSender.getResult();
+ InstallUtils.assertStatusSuccess(intent);
+ }
+
+ @Before
+ public void setup() {
+ PackageManager.disableApplicationInfoCache();
+ PackageManager.disablePackageInfoCache();
+ }
+
+ @Test
+ public void testGetAppMetadata() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ installTestApp(TestApp.A1);
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final String packageName = TestApp.A1.getPackageName();
+
+ while (state.keepRunning()) {
+ PersistableBundle bundle = pm.getAppMetadata(packageName);
+ state.pauseTiming();
+ assertEquals(bundle.size(), 1);
+ assertEquals(bundle.getString(TEST_FIELD), TEST_VALUE);
+ state.resumeTiming();
+ }
+ uninstallTestApp(packageName);
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testCheckPermissionExists() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final String packageName = TEST_ACTIVITY.getPackageName();
+
+ while (state.keepRunning()) {
+ int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testCheckPermissionExistsWithFiltering() {
+ testCheckPermissionExists();
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testCheckPermissionDoesntExist() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final String packageName = TEST_ACTIVITY.getPackageName();
+
+ while (state.keepRunning()) {
+ int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testCheckPermissionDoesntExistWithFiltering() {
+ testCheckPermissionDoesntExist();
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testQueryIntentActivities() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final Intent intent = new Intent("com.android.perftests.core.PERFTEST");
+
+ while (state.keepRunning()) {
+ pm.queryIntentActivities(intent, 0);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testQueryIntentActivitiesWithFiltering() {
+ testQueryIntentActivities();
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetPackageInfo() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+ while (state.keepRunning()) {
+ pm.getPackageInfo(TEST_ACTIVITY.getPackageName(), 0);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetPackageInfoWithFiltering() throws Exception {
+ testGetPackageInfo();
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetApplicationInfo() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+ while (state.keepRunning()) {
+ pm.getApplicationInfo(TEST_ACTIVITY.getPackageName(), 0);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetApplicationInfoWithFiltering() throws Exception {
+ testGetApplicationInfo();
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetActivityInfo() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+ while (state.keepRunning()) {
+ pm.getActivityInfo(TEST_ACTIVITY, 0);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetActivityInfoWithFiltering() throws Exception {
+ testGetActivityInfo();
+ }
+
+ @Test
+ @DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetInstalledPackages() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+
+ while (state.keepRunning()) {
+ pm.getInstalledPackages(0);
+ }
+ }
+
+ @Test
+ @EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
+ public void testGetInstalledPackagesWithFiltering() throws Exception {
+ testGetInstalledPackages();
+ }
+}
diff --git a/android-34/android/os/PackageTagsList.java b/android-34/android/os/PackageTagsList.java
new file mode 100644
index 0000000..df99074
--- /dev/null
+++ b/android-34/android/os/PackageTagsList.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SuppressLint;
+import android.annotation.TestApi;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.Immutable;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A list of packages and associated attribution tags that supports easy membership checks. Supports
+ * "wildcard" attribution tags (ie, matching any attribution tag under a package) in additional to
+ * standard checks.
+ *
+ * @hide
+ */
+@TestApi
+@Immutable
+public final class PackageTagsList implements Parcelable {
+
+ // an empty set value matches any attribution tag (ie, wildcard)
+ private final ArrayMap<String, ArraySet<String>> mPackageTags;
+
+ private PackageTagsList(@NonNull ArrayMap<String, ArraySet<String>> packageTags) {
+ mPackageTags = Objects.requireNonNull(packageTags);
+ }
+
+ /**
+ * Returns true if this instance is empty;
+ */
+ public boolean isEmpty() {
+ return mPackageTags.isEmpty();
+ }
+
+ /**
+ * Returns true if the given package is found within this instance. If this returns true this
+ * does not imply anything about whether any given attribution tag under the given package name
+ * is present.
+ */
+ public boolean includes(@NonNull String packageName) {
+ return mPackageTags.containsKey(packageName);
+ }
+
+ /**
+ * Returns true if the given attribution tag is found within this instance under any package.
+ * Only returns true if the attribution tag literal is found, not if any package contains the
+ * set of all attribution tags.
+ *
+ * @hide
+ */
+ public boolean includesTag(@NonNull String attributionTag) {
+ final int size = mPackageTags.size();
+ for (int i = 0; i < size; i++) {
+ ArraySet<String> tags = mPackageTags.valueAt(i);
+ if (tags.contains(attributionTag)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if all attribution tags under the given package are contained within this
+ * instance.
+ */
+ public boolean containsAll(@NonNull String packageName) {
+ Set<String> tags = mPackageTags.get(packageName);
+ return tags != null && tags.isEmpty();
+ }
+
+ /**
+ * Returns true if the given package and attribution tag are contained within this instance.
+ */
+ public boolean contains(@NonNull String packageName, @Nullable String attributionTag) {
+ Set<String> tags = mPackageTags.get(packageName);
+ if (tags == null) {
+ return false;
+ } else if (tags.isEmpty()) {
+ // our tags are the full set, so we contain any attribution tag
+ return true;
+ } else {
+ return tags.contains(attributionTag);
+ }
+ }
+
+ /**
+ * Returns true if the given PackageTagsList is a subset of this instance.
+ */
+ public boolean contains(@NonNull PackageTagsList packageTagsList) {
+ int otherSize = packageTagsList.mPackageTags.size();
+ if (otherSize > mPackageTags.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < otherSize; i++) {
+ String packageName = packageTagsList.mPackageTags.keyAt(i);
+ ArraySet<String> tags = mPackageTags.get(packageName);
+ if (tags == null) {
+ return false;
+ }
+ if (tags.isEmpty()) {
+ // our tags are the full set, so we contain whatever the other tags are
+ continue;
+ }
+ ArraySet<String> otherTags = packageTagsList.mPackageTags.valueAt(i);
+ if (otherTags.isEmpty()) {
+ // other tags are the full set, so we can't contain them
+ return false;
+ }
+ if (!tags.containsAll(otherTags)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a list of packages.
+ *
+ * @deprecated Do not use.
+ * @hide
+ */
+ @Deprecated
+ public @NonNull Collection<String> getPackages() {
+ return new ArrayList<>(mPackageTags.keySet());
+ }
+
+ public static final @NonNull Parcelable.Creator<PackageTagsList> CREATOR =
+ new Parcelable.Creator<PackageTagsList>() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public PackageTagsList createFromParcel(Parcel in) {
+ int count = in.readInt();
+ ArrayMap<String, ArraySet<String>> packageTags = new ArrayMap<>(count);
+ for (int i = 0; i < count; i++) {
+ String key = in.readString8();
+ ArraySet<String> value = (ArraySet<String>) in.readArraySet(null);
+ packageTags.append(key, value);
+ }
+ return new PackageTagsList(packageTags);
+ }
+
+ @Override
+ public PackageTagsList[] newArray(int size) {
+ return new PackageTagsList[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ int count = mPackageTags.size();
+ parcel.writeInt(count);
+ for (int i = 0; i < count; i++) {
+ parcel.writeString8(mPackageTags.keyAt(i));
+ parcel.writeArraySet(mPackageTags.valueAt(i));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof PackageTagsList)) {
+ return false;
+ }
+
+ PackageTagsList that = (PackageTagsList) o;
+ return mPackageTags.equals(that.mPackageTags);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageTags);
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return mPackageTags.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public void dump(PrintWriter pw) {
+ int size = mPackageTags.size();
+ for (int i = 0; i < size; i++) {
+ String packageName = mPackageTags.keyAt(i);
+ pw.print(packageName);
+ pw.print("[");
+ int tagsSize = mPackageTags.valueAt(i).size();
+ if (tagsSize == 0) {
+ pw.print("*");
+ } else {
+ for (int j = 0; j < tagsSize; j++) {
+ String attributionTag = mPackageTags.valueAt(i).valueAt(j);
+ if (j > 0) {
+ pw.print(", ");
+ }
+ if (attributionTag != null && attributionTag.startsWith(packageName)) {
+ pw.print(attributionTag.substring(packageName.length()));
+ } else {
+ pw.print(attributionTag);
+ }
+ }
+ }
+ pw.println("]");
+ }
+ }
+
+ /**
+ * Builder class for {@link PackageTagsList}.
+ */
+ public static final class Builder {
+
+ private final ArrayMap<String, ArraySet<String>> mPackageTags;
+
+ /**
+ * Creates a new builder.
+ */
+ public Builder() {
+ mPackageTags = new ArrayMap<>();
+ }
+
+ /**
+ * Creates a new builder with the given initial capacity.
+ */
+ public Builder(int capacity) {
+ mPackageTags = new ArrayMap<>(capacity);
+ }
+
+ /**
+ * Adds all attribution tags under the specified package to the builder.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder add(@NonNull String packageName) {
+ mPackageTags.computeIfAbsent(packageName, p -> new ArraySet<>()).clear();
+ return this;
+ }
+
+ /**
+ * Adds the specified package and attribution tag to the builder.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder add(@NonNull String packageName, @Nullable String attributionTag) {
+ ArraySet<String> tags = mPackageTags.get(packageName);
+ if (tags == null) {
+ tags = new ArraySet<>(1);
+ tags.add(attributionTag);
+ mPackageTags.put(packageName, tags);
+ } else if (!tags.isEmpty()) {
+ tags.add(attributionTag);
+ }
+
+ return this;
+ }
+
+ /**
+ * Adds the specified package and set of attribution tags to the builder.
+ *
+ * @hide
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder add(@NonNull String packageName,
+ @NonNull Collection<String> attributionTags) {
+ if (attributionTags.isEmpty()) {
+ // the input is not allowed to specify a full set by passing in an empty collection
+ return this;
+ }
+
+ ArraySet<String> tags = mPackageTags.get(packageName);
+ if (tags == null) {
+ tags = new ArraySet<>(attributionTags);
+ mPackageTags.put(packageName, tags);
+ } else if (!tags.isEmpty()) {
+ // if we contain the full set, already done, otherwise add all the tags
+ tags.addAll(attributionTags);
+ }
+
+ return this;
+ }
+
+ /**
+ * Adds the specified {@link PackageTagsList} to the builder.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder add(@NonNull PackageTagsList packageTagsList) {
+ return add(packageTagsList.mPackageTags);
+ }
+
+ /**
+ * Adds the given map of package to attribution tags to the builder. An empty set of
+ * attribution tags is interpreted to imply all attribution tags under that package.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder add(@NonNull Map<String, ? extends Set<String>> packageTagsMap) {
+ mPackageTags.ensureCapacity(packageTagsMap.size());
+ for (Map.Entry<String, ? extends Set<String>> entry : packageTagsMap.entrySet()) {
+ Set<String> newTags = entry.getValue();
+ if (newTags.isEmpty()) {
+ add(entry.getKey());
+ } else {
+ add(entry.getKey(), newTags);
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Removes all attribution tags under the specified package from the builder.
+ *
+ * @hide
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder remove(@NonNull String packageName) {
+ mPackageTags.remove(packageName);
+ return this;
+ }
+
+ /**
+ * Removes the specified package and attribution tag from the builder if and only if the
+ * specified attribution tag is listed explicitly under the package. If the package contains
+ * all possible attribution tags, then nothing will be removed.
+ *
+ * @hide
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder remove(@NonNull String packageName,
+ @Nullable String attributionTag) {
+ ArraySet<String> tags = mPackageTags.get(packageName);
+ if (tags != null && tags.remove(attributionTag) && tags.isEmpty()) {
+ mPackageTags.remove(packageName);
+ }
+ return this;
+ }
+
+ /**
+ * Removes the specified package and set of attribution tags from the builder if and only if
+ * the specified set of attribution tags are listed explicitly under the package. If the
+ * package contains all possible attribution tags, then nothing will be removed.
+ *
+ * @hide
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder remove(@NonNull String packageName,
+ @NonNull Collection<String> attributionTags) {
+ if (attributionTags.isEmpty()) {
+ // the input is not allowed to specify a full set by passing in an empty collection
+ return this;
+ }
+
+ ArraySet<String> tags = mPackageTags.get(packageName);
+ if (tags != null && tags.removeAll(attributionTags) && tags.isEmpty()) {
+ mPackageTags.remove(packageName);
+ }
+ return this;
+ }
+
+ /**
+ * Removes the specified {@link PackageTagsList} from the builder. If a package contains all
+ * possible attribution tags, it will only be removed if the package in the removed
+ * {@link PackageTagsList} also contains all possible attribution tags.
+ *
+ * @hide
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder remove(@NonNull PackageTagsList packageTagsList) {
+ return remove(packageTagsList.mPackageTags);
+ }
+
+ /**
+ * Removes the given map of package to attribution tags to the builder. An empty set of
+ * attribution tags is interpreted to imply all attribution tags under that package. If a
+ * package contains all possible attribution tags, it will only be removed if the package in
+ * the removed map also contains all possible attribution tags.
+ *
+ * @hide
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder remove(@NonNull Map<String, ? extends Set<String>> packageTagsMap) {
+ for (Map.Entry<String, ? extends Set<String>> entry : packageTagsMap.entrySet()) {
+ Set<String> removedTags = entry.getValue();
+ if (removedTags.isEmpty()) {
+ // if removing the full set, drop the package completely
+ remove(entry.getKey());
+ } else {
+ remove(entry.getKey(), removedTags);
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Clears the builder.
+ */
+ public @NonNull Builder clear() {
+ mPackageTags.clear();
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link PackageTagsList}.
+ */
+ public @NonNull PackageTagsList build() {
+ return new PackageTagsList(copy(mPackageTags));
+ }
+
+ private static ArrayMap<String, ArraySet<String>> copy(
+ ArrayMap<String, ArraySet<String>> value) {
+ int size = value.size();
+ ArrayMap<String, ArraySet<String>> copy = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String packageName = value.keyAt(i);
+ ArraySet<String> tags = new ArraySet<>(Objects.requireNonNull(value.valueAt(i)));
+ copy.append(packageName, tags);
+ }
+ return copy;
+ }
+ }
+}
diff --git a/android-34/android/os/Parcel.java b/android-34/android/os/Parcel.java
new file mode 100644
index 0000000..e784c26
--- /dev/null
+++ b/android-34/android/os/Parcel.java
@@ -0,0 +1,5552 @@
+/*
+ * 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 com.android.internal.util.Preconditions.checkArgument;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.AppOpsManager;
+import android.compat.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.MathUtils;
+import android.util.Pair;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+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.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+
+/**
+ * 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 #writeInterfaceArray(T[])}, {@link #readInterfaceArray(T[], Function)},
+ * {@link #createInterfaceArray(IntFunction, Function)},
+ * {@link #writeBinderList(List)}, {@link #readBinderList(List)},
+ * {@link #createBinderArrayList()},
+ * {@link #writeInterfaceList(List)}, {@link #readInterfaceList(List, Function)},
+ * {@link #createInterfaceArrayList(Function)}.</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>Parcelable 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)}.
+ *
+ * <h3>Restricted Parcelable Containers</h3>
+ *
+ * <p>A final class of methods are for reading standard Java containers of restricted types.
+ * These methods replace methods for reading containers of arbitrary types from previous section
+ * starting from Android {@link Build.VERSION_CODES#TIRAMISU}. The pairing writing methods are
+ * still the same from previous section.
+ * These methods accepts additional {@code clazz} parameters as the required types.
+ * The Restricted Parcelable container methods are {@link #readArray(ClassLoader, Class)},
+ * {@link #readList(List, ClassLoader, Class)},
+ * {@link #readArrayList(ClassLoader, Class)},
+ * {@link #readMap(Map, ClassLoader, Class, Class)},
+ * {@link #readSparseArray(ClassLoader, Class)}.
+ */
+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;
+ private boolean mRecycled = false;
+
+ /** @hide */
+ @TestApi
+ public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1 << 0;
+
+ /** @hide */
+ @TestApi
+ public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 1 << 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT,
+ FLAG_PROPAGATE_ALLOW_BLOCKING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ParcelFlags {}
+
+ @ParcelFlags
+ private int mFlags;
+
+ /**
+ * 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 Object sPoolSync = new Object();
+
+ /** Next item in the linked list pool, if any */
+ @GuardedBy("sPoolSync")
+ private Parcel mPoolNext;
+
+ /** Head of a linked list pool of {@link Parcel} objects */
+ @GuardedBy("sPoolSync")
+ private static Parcel sOwnedPool;
+ /** Head of a linked list pool of {@link Parcel} objects */
+ @GuardedBy("sPoolSync")
+ private static Parcel sHolderPool;
+
+ /** Total size of pool with head at {@link #sOwnedPool} */
+ @GuardedBy("sPoolSync")
+ private static int sOwnedPoolSize = 0;
+ /** Total size of pool with head at {@link #sHolderPool} */
+ @GuardedBy("sPoolSync")
+ private static int sHolderPoolSize = 0;
+
+ /**
+ * We're willing to pool up to 32 objects, which is sized to accommodate
+ * both a data and reply Parcel for the maximum of 16 Binder threads.
+ */
+ private static final int POOL_SIZE = 32;
+
+ // 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; // length-prefixed
+ private static final int VAL_BUNDLE = 3;
+ private static final int VAL_PARCELABLE = 4; // length-prefixed
+ 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; // length-prefixed
+ private static final int VAL_SPARSEARRAY = 12; // length-prefixed
+ 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; // length-prefixed
+ private static final int VAL_OBJECTARRAY = 17; // length-prefixed
+ 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; // length-prefixed
+ 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;
+ private static final int VAL_CHAR = 29;
+ private static final int VAL_SHORTARRAY = 30;
+ private static final int VAL_CHARARRAY = 31;
+ private static final int VAL_FLOATARRAY = 32;
+
+ // 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;
+ /** @hide */
+ // WARNING: DO NOT add more 'reply' headers. These also need to add work to native
+ // code and this encodes extra information in object statuses. If we need to expand
+ // this design, we should add a generic way to attach parcelables/structured parcelables
+ // to transactions which can work across languages.
+ public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; // special; see below
+ // WARNING: DO NOT add more 'reply' headers. These also need to add work to native
+ // code and this encodes extra information in object statuses. If we need to expand
+ // this design, we should add a generic way to attach parcelables/structured parcelables
+ // to transactions which can work across languages.
+ private static final int EX_HAS_STRICTMODE_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 void nativeMarkSensitive(long nativePtr);
+ @FastNative
+ private static native void nativeMarkForBinder(long nativePtr, IBinder binder);
+ @CriticalNative
+ private static native boolean nativeIsForRpc(long nativePtr);
+ @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 void 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);
+ @CriticalNative
+ private static native int nativeWriteInt(long nativePtr, int val);
+ @CriticalNative
+ private static native int nativeWriteLong(long nativePtr, long val);
+ @CriticalNative
+ private static native int nativeWriteFloat(long nativePtr, float val);
+ @CriticalNative
+ private static native int nativeWriteDouble(long nativePtr, double val);
+ private static native void nativeSignalExceptionForError(int error);
+ @FastNative
+ private static native void nativeWriteString8(long nativePtr, String val);
+ @FastNative
+ private static native void nativeWriteString16(long nativePtr, String val);
+ @FastNative
+ private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
+ @FastNative
+ private static native void 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);
+ @FastNative
+ private static native String nativeReadString8(long nativePtr);
+ @FastNative
+ private static native String nativeReadString16(long nativePtr);
+ @FastNative
+ private static native IBinder nativeReadStrongBinder(long nativePtr);
+ @FastNative
+ private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
+
+ private static native long nativeCreate();
+ private static native void nativeFreeBuffer(long nativePtr);
+ private static native void nativeDestroy(long nativePtr);
+
+ private static native byte[] nativeMarshall(long nativePtr);
+ private static native void nativeUnmarshall(
+ long nativePtr, byte[] data, int offset, int length);
+ private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
+ private static native boolean nativeCompareDataInRange(
+ long ptrA, int offsetA, long ptrB, int offsetB, int length);
+ private static native void nativeAppendFrom(
+ long thisNativePtr, long otherNativePtr, int offset, int length);
+ @CriticalNative
+ private static native boolean nativeHasFileDescriptors(long nativePtr);
+ private static native boolean nativeHasFileDescriptorsInRange(
+ long nativePtr, int offset, int length);
+ 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 nativeGetOpenAshmemSize(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 {
+
+ @UnsupportedAppUsage
+ public 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 writeString8(Parcel p, String s) {
+ p.writeString8NoHelper(s);
+ }
+
+ public void writeString16(Parcel p, String s) {
+ p.writeString16NoHelper(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 readString8(Parcel p) {
+ return p.readString8NoHelper();
+ }
+
+ public String readString16(Parcel p) {
+ return p.readString16NoHelper();
+ }
+ }
+
+ private ReadWriteHelper mReadWriteHelper = ReadWriteHelper.DEFAULT;
+
+ /**
+ * Retrieve a new Parcel object from the pool.
+ */
+ @NonNull
+ public static Parcel obtain() {
+ Parcel res = null;
+ synchronized (sPoolSync) {
+ if (sOwnedPool != null) {
+ res = sOwnedPool;
+ sOwnedPool = res.mPoolNext;
+ res.mPoolNext = null;
+ sOwnedPoolSize--;
+ }
+ }
+
+ // When no cache found above, create from scratch; otherwise prepare the
+ // cached object to be used
+ if (res == null) {
+ res = new Parcel(0);
+ } else {
+ res.mRecycled = false;
+ if (DEBUG_RECYCLE) {
+ res.mStack = new RuntimeException();
+ }
+ res.mReadWriteHelper = ReadWriteHelper.DEFAULT;
+ }
+ return res;
+ }
+
+ /**
+ * Retrieve a new Parcel object from the pool for use with a specific binder.
+ *
+ * Associate this parcel with a binder object. This marks the parcel as being prepared for a
+ * transaction on this specific binder object. Based on this, the format of the wire binder
+ * protocol may change. For future compatibility, it is recommended to use this for all
+ * Parcels.
+ */
+ @NonNull
+ public static Parcel obtain(@NonNull IBinder binder) {
+ Parcel parcel = Parcel.obtain();
+ parcel.markForBinder(binder);
+ return parcel;
+ }
+
+ /**
+ * Put a Parcel object back into the pool. You must not touch
+ * the object after this call.
+ */
+ public final void recycle() {
+ if (mRecycled) {
+ Log.wtf(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: "
+ + Log.getStackTraceString(new Throwable())
+ + " Original recycle call (if DEBUG_RECYCLE): ", mStack);
+
+ return;
+ }
+ mRecycled = true;
+
+ // We try to reset the entire object here, but in order to be
+ // able to print a stack when a Parcel is recycled twice, that
+ // is cleared in obtain instead.
+
+ mClassCookies = null;
+ freeBuffer();
+
+ if (mOwnsNativeParcelObject) {
+ synchronized (sPoolSync) {
+ if (sOwnedPoolSize < POOL_SIZE) {
+ mPoolNext = sOwnedPool;
+ sOwnedPool = this;
+ sOwnedPoolSize++;
+ }
+ }
+ } else {
+ mNativePtr = 0;
+ synchronized (sPoolSync) {
+ if (sHolderPoolSize < POOL_SIZE) {
+ mPoolNext = sHolderPool;
+ sHolderPool = this;
+ sHolderPoolSize++;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static native long getGlobalAllocSize();
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static native long getGlobalAllocCount();
+
+ /**
+ * Parcel data should be zero'd before realloc'd or deleted.
+ *
+ * Note: currently this feature requires multiple things to work in concert:
+ * - markSensitive must be called on every relative Parcel
+ * - FLAG_CLEAR_BUF must be passed into the kernel
+ * This requires having code which does the right thing in every method and in every backend
+ * of AIDL. Rather than exposing this API, it should be replaced with a single API on
+ * IBinder objects which can be called once, and the information should be fed into the
+ * Parcel using markForBinder APIs. In terms of code size and number of API calls, this is
+ * much more extensible.
+ *
+ * @hide
+ */
+ public final void markSensitive() {
+ nativeMarkSensitive(mNativePtr);
+ }
+
+ /**
+ * @hide
+ */
+ private void markForBinder(@NonNull IBinder binder) {
+ nativeMarkForBinder(mNativePtr, binder);
+ }
+
+ /**
+ * Whether this Parcel is written for an RPC transaction.
+ *
+ * @hide
+ */
+ public final boolean isForRpc() {
+ return nativeIsForRpc(mNativePtr);
+ }
+
+ /** @hide */
+ @ParcelFlags
+ @TestApi
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ public void setFlags(@ParcelFlags int flags) {
+ mFlags = flags;
+ }
+
+ /** @hide */
+ public void addFlags(@ParcelFlags int flags) {
+ mFlags |= flags;
+ }
+
+ /** @hide */
+ private boolean hasFlags(@ParcelFlags int flags) {
+ return (mFlags & flags) == flags;
+ }
+
+ /**
+ * This method is used by the AIDL compiler for system components. Not intended to be
+ * used by non-system apps.
+ */
+ // Note: Ideally this method should be @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES),
+ // but we need to make this method public due to the way the aidl compiler is compiled.
+ // We don't really need to protect it; even if 3p / non-system apps, nothing would happen.
+ // This would only work when used on a reply parcel by a binder object that's allowed-blocking.
+ public void setPropagateAllowBlocking() {
+ addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING);
+ }
+
+ /**
+ * Returns the total amount of data contained in the parcel.
+ */
+ public 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) {
+ 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);
+ }
+
+ /**
+ * Fills the raw bytes of this Parcel with the supplied data.
+ */
+ public final void unmarshall(@NonNull byte[] data, int offset, int length) {
+ nativeUnmarshall(mNativePtr, data, offset, length);
+ }
+
+ public final void appendFrom(Parcel parcel, int offset, int length) {
+ nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
+ }
+
+ /** @hide */
+ public int compareData(Parcel other) {
+ return nativeCompareData(mNativePtr, other.mNativePtr);
+ }
+
+ /** @hide */
+ public static boolean compareData(Parcel a, int offsetA, Parcel b, int offsetB, int length) {
+ return nativeCompareDataInRange(a.mNativePtr, offsetA, b.mNativePtr, offsetB, length);
+ }
+
+ /** @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 boolean hasFileDescriptors() {
+ return nativeHasFileDescriptors(mNativePtr);
+ }
+
+ /**
+ * Report whether the parcel contains any marshalled file descriptors in the range defined by
+ * {@code offset} and {@code length}.
+ *
+ * @param offset The offset from which the range starts. Should be between 0 and
+ * {@link #dataSize()}.
+ * @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code
+ * offset}.
+ * @return whether there are file descriptors or not.
+ * @throws IllegalArgumentException if the parameters are out of the permitted ranges.
+ */
+ public boolean hasFileDescriptors(int offset, int length) {
+ return nativeHasFileDescriptorsInRange(mNativePtr, offset, length);
+ }
+
+ /**
+ * Check if the object has file descriptors.
+ *
+ * <p>Objects supported are {@link Parcel} and objects that can be passed to {@link
+ * #writeValue(Object)}}
+ *
+ * <p>For most cases, it will use the self-reported {@link Parcelable#describeContents()} method
+ * for that.
+ *
+ * @throws IllegalArgumentException if you provide any object not supported by above methods
+ * (including if the unsupported object is inside a nested container).
+ *
+ * @hide
+ */
+ public static boolean hasFileDescriptors(Object value) {
+ if (value instanceof Parcel) {
+ Parcel parcel = (Parcel) value;
+ if (parcel.hasFileDescriptors()) {
+ return true;
+ }
+ } else if (value instanceof LazyValue) {
+ LazyValue lazy = (LazyValue) value;
+ if (lazy.hasFileDescriptors()) {
+ return true;
+ }
+ } else if (value instanceof Parcelable) {
+ Parcelable parcelable = (Parcelable) value;
+ if ((parcelable.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+ return true;
+ }
+ } else if (value instanceof ArrayMap<?, ?>) {
+ ArrayMap<?, ?> map = (ArrayMap<?, ?>) value;
+ for (int i = 0, n = map.size(); i < n; i++) {
+ if (hasFileDescriptors(map.keyAt(i))
+ || hasFileDescriptors(map.valueAt(i))) {
+ return true;
+ }
+ }
+ } else if (value instanceof Map<?, ?>) {
+ Map<?, ?> map = (Map<?, ?>) value;
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ if (hasFileDescriptors(entry.getKey())
+ || hasFileDescriptors(entry.getValue())) {
+ return true;
+ }
+ }
+ } else if (value instanceof List<?>) {
+ List<?> list = (List<?>) value;
+ for (int i = 0, n = list.size(); i < n; i++) {
+ if (hasFileDescriptors(list.get(i))) {
+ return true;
+ }
+ }
+ } else if (value instanceof SparseArray<?>) {
+ SparseArray<?> array = (SparseArray<?>) value;
+ for (int i = 0, n = array.size(); i < n; i++) {
+ if (hasFileDescriptors(array.valueAt(i))) {
+ return true;
+ }
+ }
+ } else if (value instanceof Object[]) {
+ Object[] array = (Object[]) value;
+ for (int i = 0, n = array.length; i < n; i++) {
+ if (hasFileDescriptors(array[i])) {
+ return true;
+ }
+ }
+ } else {
+ getValueType(value); // Will throw if value is not supported
+ }
+ return false;
+ }
+
+ /**
+ * 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. This is typically written
+ * at the beginning of transactions as a header.
+ */
+ public final void writeInterfaceToken(@NonNull String interfaceName) {
+ nativeWriteInterfaceToken(mNativePtr, interfaceName);
+ }
+
+ /**
+ * Read the header written by writeInterfaceToken and verify it matches
+ * the interface name in question. If the wrong interface type is present,
+ * {@link SecurityException} is thrown. When used over binder, this exception
+ * should propagate to the caller.
+ */
+ public final void enforceInterface(@NonNull String interfaceName) {
+ nativeEnforceInterface(mNativePtr, interfaceName);
+ }
+
+ /**
+ * Verify there are no bytes left to be read on the Parcel.
+ *
+ * @throws BadParcelableException If the current position hasn't reached the end of the Parcel.
+ * When used over binder, this exception should propagate to the caller.
+ */
+ public void enforceNoDataAvail() {
+ final int n = dataAvail();
+ if (n > 0) {
+ throw new BadParcelableException("Parcel data not fully consumed, unread size: " + n);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <p> If the blob is small, then it is stored in-place, otherwise it is transferred by way of
+ * an anonymous shared memory region. If you prefer send in-place, please use
+ * {@link #writeByteArray(byte[])}.
+ *
+ * @param b Bytes to place into the parcel.
+ *
+ * @see #readBlob()
+ */
+ 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.
+ *
+ * <p> If the blob is small, then it is stored in-place, otherwise it is transferred by way of
+ * an anonymous shared memory region. If you prefer send in-place, please use
+ * {@link #writeByteArray(byte[], int, int)}.
+ *
+ * @param b Bytes to place into the parcel.
+ * @param offset Index of first byte to be written.
+ * @param len Number of bytes to write.
+ *
+ * @see #readBlob()
+ */
+ 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);
+ }
+
+ // The OK status from system/core/libutils/include/utils/Errors.h .
+ // We shall pass all other error codes back to native for throwing exceptions. The error
+ // check is done in Java to allow using @CriticalNative calls for the success path.
+ private static final int OK = 0;
+
+ /**
+ * Write an integer value into the parcel at the current dataPosition(),
+ * growing dataCapacity() if needed.
+ */
+ public final void writeInt(int val) {
+ int err = nativeWriteInt(mNativePtr, val);
+ if (err != OK) {
+ nativeSignalExceptionForError(err);
+ }
+ }
+
+ /**
+ * Write a long integer value into the parcel at the current dataPosition(),
+ * growing dataCapacity() if needed.
+ */
+ public final void writeLong(long val) {
+ int err = nativeWriteLong(mNativePtr, val);
+ if (err != OK) {
+ nativeSignalExceptionForError(err);
+ }
+ }
+
+ /**
+ * Write a floating point value into the parcel at the current
+ * dataPosition(), growing dataCapacity() if needed.
+ */
+ public final void writeFloat(float val) {
+ int err = nativeWriteFloat(mNativePtr, val);
+ if (err != OK) {
+ nativeSignalExceptionForError(err);
+ }
+ }
+
+ /**
+ * Write a double precision floating point value into the parcel at the
+ * current dataPosition(), growing dataCapacity() if needed.
+ */
+ public final void writeDouble(double val) {
+ int err = nativeWriteDouble(mNativePtr, val);
+ if (err != OK) {
+ nativeSignalExceptionForError(err);
+ }
+ }
+
+ /**
+ * Write a string value into the parcel at the current dataPosition(),
+ * growing dataCapacity() if needed.
+ */
+ public final void writeString(@Nullable String val) {
+ writeString16(val);
+ }
+
+ /** {@hide} */
+ public final void writeString8(@Nullable String val) {
+ mReadWriteHelper.writeString8(this, val);
+ }
+
+ /** {@hide} */
+ public final void writeString16(@Nullable String val) {
+ mReadWriteHelper.writeString16(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) {
+ writeString16NoHelper(val);
+ }
+
+ /** {@hide} */
+ public void writeString8NoHelper(@Nullable String val) {
+ nativeWriteString8(mNativePtr, val);
+ }
+
+ /** {@hide} */
+ public void writeString16NoHelper(@Nullable String val) {
+ nativeWriteString16(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) {
+ nativeWriteFileDescriptor(mNativePtr, val);
+ }
+
+ /**
+ * {@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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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");
+ }
+ }
+
+ /** @hide */
+ public void writeShortArray(@Nullable short[] val) {
+ if (val != null) {
+ int n = val.length;
+ writeInt(n);
+ for (int i = 0; i < n; i++) {
+ writeInt(val[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
+ /** @hide */
+ @Nullable
+ public short[] createShortArray() {
+ int n = readInt();
+ if (n >= 0 && n <= (dataAvail() >> 2)) {
+ short[] val = new short[n];
+ for (int i = 0; i < n; i++) {
+ val[i] = (short) readInt();
+ }
+ return val;
+ } else {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public void readShortArray(@NonNull short[] val) {
+ int n = readInt();
+ if (n == val.length) {
+ for (int i = 0; i < n; i++) {
+ val[i] = (short) readInt();
+ }
+ } 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) {
+ writeString16Array(val);
+ }
+
+ @Nullable
+ public final String[] createStringArray() {
+ return createString16Array();
+ }
+
+ public final void readStringArray(@NonNull String[] val) {
+ readString16Array(val);
+ }
+
+ /** {@hide} */
+ public final void writeString8Array(@Nullable String[] val) {
+ if (val != null) {
+ int N = val.length;
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeString8(val[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
+ /** {@hide} */
+ @Nullable
+ public final String[] createString8Array() {
+ int N = readInt();
+ if (N >= 0) {
+ String[] val = new String[N];
+ for (int i=0; i<N; i++) {
+ val[i] = readString8();
+ }
+ return val;
+ } else {
+ return null;
+ }
+ }
+
+ /** {@hide} */
+ public final void readString8Array(@NonNull String[] val) {
+ int N = readInt();
+ if (N == val.length) {
+ for (int i=0; i<N; i++) {
+ val[i] = readString8();
+ }
+ } else {
+ throw new RuntimeException("bad array lengths");
+ }
+ }
+
+ /** {@hide} */
+ public final void writeString16Array(@Nullable String[] val) {
+ if (val != null) {
+ int N = val.length;
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeString16(val[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
+ /** {@hide} */
+ @Nullable
+ public final String[] createString16Array() {
+ int N = readInt();
+ if (N >= 0) {
+ String[] val = new String[N];
+ for (int i=0; i<N; i++) {
+ val[i] = readString16();
+ }
+ return val;
+ } else {
+ return null;
+ }
+ }
+
+ /** {@hide} */
+ public final void readString16Array(@NonNull String[] val) {
+ int N = readInt();
+ if (N == val.length) {
+ for (int i=0; i<N; i++) {
+ val[i] = readString16();
+ }
+ } 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);
+ }
+ }
+
+ /**
+ * Flatten a homogeneous array containing an IInterface 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 IInterface.
+ *
+ * @param val The array of objects to be written.
+ *
+ * @see #createInterfaceArray
+ * @see #readInterfaceArray
+ * @see IInterface
+ */
+ public final <T extends IInterface> void writeInterfaceArray(
+ @SuppressLint("ArrayReturn") @Nullable T[] val) {
+ if (val != null) {
+ int N = val.length;
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeStrongInterface(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");
+ }
+ }
+
+ /**
+ * Read and return a new array of T (IInterface) from the parcel.
+ *
+ * @return the IInterface array of type T
+ * @param newArray a function to create an array of T with a given length
+ * @param asInterface a function to convert IBinder object into T (IInterface)
+ */
+ @SuppressLint({"ArrayReturn", "NullableCollection", "SamShouldBeLast"})
+ @Nullable
+ public final <T extends IInterface> T[] createInterfaceArray(
+ @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) {
+ int N = readInt();
+ if (N >= 0) {
+ T[] val = newArray.apply(N);
+ for (int i=0; i<N; i++) {
+ val[i] = asInterface.apply(readStrongBinder());
+ }
+ return val;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Read an array of T (IInterface) from a parcel.
+ *
+ * @param asInterface a function to convert IBinder object into T (IInterface)
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the length of `val`
+ * mismatches the number of items in the parcel.
+ */
+ public final <T extends IInterface> void readInterfaceArray(
+ @SuppressLint("ArrayReturn") @NonNull T[] val,
+ @NonNull Function<IBinder, T> asInterface) {
+ int N = readInt();
+ if (N == val.length) {
+ for (int i=0; i<N; i++) {
+ val[i] = asInterface.apply(readStrongBinder());
+ }
+ } else {
+ throw new BadParcelableException("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);
+ }
+ }
+
+ /**
+ * 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.
+ * @param parcelableFlags Contextual flags as per
+ * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+ *
+ * @see #createTypedArrayList
+ * @see #readTypedList
+ * @see Parcelable
+ */
+ 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 T (IInterface) objects into this parcel
+ * at the current position. They can later be retrieved with
+ * {@link #createInterfaceArrayList} or {@link #readInterfaceList}.
+ *
+ * @see #createInterfaceArrayList
+ * @see #readInterfaceList
+ */
+ public final <T extends IInterface> void writeInterfaceList(@Nullable List<T> val) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ int N = val.size();
+ int i=0;
+ writeInt(N);
+ while (i < N) {
+ writeStrongInterface(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 homogeneous multi-dimensional array with fixed-size. This delegates to other
+ * APIs to write a one-dimensional array. Use {@link #readFixedArray(Object)} or
+ * {@link #createFixedArray(Class, int[])} with the same dimensions to unmarshal.
+ *
+ * @param val The array to be written.
+ * @param parcelableFlags Contextual flags as per
+ * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+ * Used only if val is an array of Parcelable objects.
+ * @param dimensions an array of int representing length of each dimension. The array should be
+ * sized with the exact size of dimensions.
+ *
+ * @see #readFixedArray
+ * @see #createFixedArray createFixedArray(Class<T>, Parcelable.Creator<S>, int...)
+ * @see #writeBooleanArray
+ * @see #writeByteArray
+ * @see #writeCharArray
+ * @see #writeIntArray
+ * @see #writeLongArray
+ * @see #writeFloatArray
+ * @see #writeDoubleArray
+ * @see #writeBinderArray
+ * @see #writeInterfaceArray
+ * @see #writeTypedArray
+ * @throws BadParcelableException If the array's component type is not supported or if its
+ * size doesn't match with the given dimensions.
+ */
+ public <T> void writeFixedArray(@Nullable T val, int parcelableFlags,
+ @NonNull int... dimensions) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ writeFixedArrayInternal(val, parcelableFlags, /*index=*/0, dimensions);
+ }
+
+ private <T> void writeFixedArrayInternal(T val, int parcelableFlags, int index,
+ int[] dimensions) {
+ if (index >= dimensions.length) {
+ throw new BadParcelableException("Array has more dimensions than expected: "
+ + dimensions.length);
+ }
+
+ int length = dimensions[index];
+
+ // val should be an array of length N
+ if (val == null) {
+ throw new BadParcelableException("Non-null array shouldn't have a null array.");
+ }
+ if (!val.getClass().isArray()) {
+ throw new BadParcelableException("Not an array: " + val);
+ }
+ if (Array.getLength(val) != length) {
+ throw new BadParcelableException("bad length: expected " + length + ", but got "
+ + Array.getLength(val));
+ }
+
+ // Delegates to other writers if this is a one-dimensional array.
+ // Otherwise, write component arrays with recursive calls.
+
+ final Class<?> componentType = val.getClass().getComponentType();
+ if (!componentType.isArray() && index + 1 != dimensions.length) {
+ throw new BadParcelableException("Array has fewer dimensions than expected: "
+ + dimensions.length);
+ }
+ if (componentType == boolean.class) {
+ writeBooleanArray((boolean[]) val);
+ } else if (componentType == byte.class) {
+ writeByteArray((byte[]) val);
+ } else if (componentType == char.class) {
+ writeCharArray((char[]) val);
+ } else if (componentType == int.class) {
+ writeIntArray((int[]) val);
+ } else if (componentType == long.class) {
+ writeLongArray((long[]) val);
+ } else if (componentType == float.class) {
+ writeFloatArray((float[]) val);
+ } else if (componentType == double.class) {
+ writeDoubleArray((double[]) val);
+ } else if (componentType == IBinder.class) {
+ writeBinderArray((IBinder[]) val);
+ } else if (IInterface.class.isAssignableFrom(componentType)) {
+ writeInterfaceArray((IInterface[]) val);
+ } else if (Parcelable.class.isAssignableFrom(componentType)) {
+ writeTypedArray((Parcelable[]) val, parcelableFlags);
+ } else if (componentType.isArray()) {
+ writeInt(length);
+ for (int i = 0; i < length; i++) {
+ writeFixedArrayInternal(Array.get(val, i), parcelableFlags, index + 1,
+ dimensions);
+ }
+ } else {
+ throw new BadParcelableException("unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * 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 instanceof LazyValue) {
+ LazyValue value = (LazyValue) v;
+ value.writeToParcel(this);
+ return;
+ }
+ int type = getValueType(v);
+ writeInt(type);
+ if (isLengthPrefixed(type)) {
+ // Length
+ int length = dataPosition();
+ writeInt(-1); // Placeholder
+ // Object
+ int start = dataPosition();
+ writeValue(type, v);
+ int end = dataPosition();
+ // Backpatch length
+ setDataPosition(length);
+ writeInt(end - start);
+ setDataPosition(end);
+ } else {
+ writeValue(type, v);
+ }
+ }
+
+ /** @hide */
+ public static int getValueType(@Nullable Object v) {
+ if (v == null) {
+ return VAL_NULL;
+ } else if (v instanceof String) {
+ return VAL_STRING;
+ } else if (v instanceof Integer) {
+ return VAL_INTEGER;
+ } else if (v instanceof Map) {
+ return VAL_MAP;
+ } else if (v instanceof Bundle) {
+ // Must be before Parcelable
+ return VAL_BUNDLE;
+ } else if (v instanceof PersistableBundle) {
+ // Must be before Parcelable
+ return VAL_PERSISTABLEBUNDLE;
+ } else if (v instanceof SizeF) {
+ // Must be before Parcelable
+ return VAL_SIZEF;
+ } else if (v instanceof Parcelable) {
+ // IMPOTANT: cases for classes that implement Parcelable must
+ // come before the Parcelable case, so that their speci fic VAL_*
+ // types will be written.
+ return VAL_PARCELABLE;
+ } else if (v instanceof Short) {
+ return VAL_SHORT;
+ } else if (v instanceof Long) {
+ return VAL_LONG;
+ } else if (v instanceof Float) {
+ return VAL_FLOAT;
+ } else if (v instanceof Double) {
+ return VAL_DOUBLE;
+ } else if (v instanceof Boolean) {
+ return VAL_BOOLEAN;
+ } else if (v instanceof CharSequence) {
+ // Must be after String
+ return VAL_CHARSEQUENCE;
+ } else if (v instanceof List) {
+ return VAL_LIST;
+ } else if (v instanceof SparseArray) {
+ return VAL_SPARSEARRAY;
+ } else if (v instanceof boolean[]) {
+ return VAL_BOOLEANARRAY;
+ } else if (v instanceof byte[]) {
+ return VAL_BYTEARRAY;
+ } else if (v instanceof String[]) {
+ return VAL_STRINGARRAY;
+ } else if (v instanceof CharSequence[]) {
+ // Must be after String[] and before Object[]
+ return VAL_CHARSEQUENCEARRAY;
+ } else if (v instanceof IBinder) {
+ return VAL_IBINDER;
+ } else if (v instanceof Parcelable[]) {
+ return VAL_PARCELABLEARRAY;
+ } else if (v instanceof int[]) {
+ return VAL_INTARRAY;
+ } else if (v instanceof long[]) {
+ return VAL_LONGARRAY;
+ } else if (v instanceof Byte) {
+ return VAL_BYTE;
+ } else if (v instanceof Size) {
+ return VAL_SIZE;
+ } else if (v instanceof double[]) {
+ return VAL_DOUBLEARRAY;
+ } else if (v instanceof Character) {
+ return VAL_CHAR;
+ } else if (v instanceof short[]) {
+ return VAL_SHORTARRAY;
+ } else if (v instanceof char[]) {
+ return VAL_CHARARRAY;
+ } else if (v instanceof float[]) {
+ return VAL_FLOATARRAY;
+ } 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.
+ return VAL_OBJECTARRAY;
+ } else if (v instanceof Serializable) {
+ // Must be last
+ return VAL_SERIALIZABLE;
+ } else {
+ throw new IllegalArgumentException("Parcel: unknown type for value " + v);
+ }
+ }
+ }
+ /**
+ * Writes value {@code v} in the parcel. This does NOT write the int representing the type
+ * first.
+ *
+ * @hide
+ */
+ public void writeValue(int type, @Nullable Object v) {
+ switch (type) {
+ case VAL_NULL:
+ break;
+ case VAL_STRING:
+ writeString((String) v);
+ break;
+ case VAL_INTEGER:
+ writeInt((Integer) v);
+ break;
+ case VAL_MAP:
+ writeMap((Map) v);
+ break;
+ case VAL_BUNDLE:
+ writeBundle((Bundle) v);
+ break;
+ case VAL_PERSISTABLEBUNDLE:
+ writePersistableBundle((PersistableBundle) v);
+ break;
+ case VAL_PARCELABLE:
+ writeParcelable((Parcelable) v, 0);
+ break;
+ case VAL_SHORT:
+ writeInt(((Short) v).intValue());
+ break;
+ case VAL_LONG:
+ writeLong((Long) v);
+ break;
+ case VAL_FLOAT:
+ writeFloat((Float) v);
+ break;
+ case VAL_DOUBLE:
+ writeDouble((Double) v);
+ break;
+ case VAL_BOOLEAN:
+ writeInt((Boolean) v ? 1 : 0);
+ break;
+ case VAL_CHARSEQUENCE:
+ writeCharSequence((CharSequence) v);
+ break;
+ case VAL_LIST:
+ writeList((List) v);
+ break;
+ case VAL_SPARSEARRAY:
+ writeSparseArray((SparseArray) v);
+ break;
+ case VAL_BOOLEANARRAY:
+ writeBooleanArray((boolean[]) v);
+ break;
+ case VAL_BYTEARRAY:
+ writeByteArray((byte[]) v);
+ break;
+ case VAL_STRINGARRAY:
+ writeStringArray((String[]) v);
+ break;
+ case VAL_CHARSEQUENCEARRAY:
+ writeCharSequenceArray((CharSequence[]) v);
+ break;
+ case VAL_IBINDER:
+ writeStrongBinder((IBinder) v);
+ break;
+ case VAL_PARCELABLEARRAY:
+ writeParcelableArray((Parcelable[]) v, 0);
+ break;
+ case VAL_INTARRAY:
+ writeIntArray((int[]) v);
+ break;
+ case VAL_LONGARRAY:
+ writeLongArray((long[]) v);
+ break;
+ case VAL_BYTE:
+ writeInt((Byte) v);
+ break;
+ case VAL_SIZE:
+ writeSize((Size) v);
+ break;
+ case VAL_SIZEF:
+ writeSizeF((SizeF) v);
+ break;
+ case VAL_DOUBLEARRAY:
+ writeDoubleArray((double[]) v);
+ break;
+ case VAL_CHAR:
+ writeInt((Character) v);
+ break;
+ case VAL_SHORTARRAY:
+ writeShortArray((short[]) v);
+ break;
+ case VAL_CHARARRAY:
+ writeCharArray((char[]) v);
+ break;
+ case VAL_FLOATARRAY:
+ writeFloatArray((float[]) v);
+ break;
+ case VAL_OBJECTARRAY:
+ writeArray((Object[]) v);
+ break;
+ case VAL_SERIALIZABLE:
+ writeSerializable((Serializable) v);
+ break;
+ default:
+ 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);
+ }
+
+ /**
+ * Flatten the name of the class of the Parcelable into this Parcel.
+ *
+ * @param p The Parcelable object to be written.
+ * @see #readParcelableCreator
+ */
+ public final void writeParcelableCreator(@NonNull Parcelable p) {
+ String name = p.getClass().getName();
+ writeString(name);
+ }
+
+ /**
+ * A map used by {@link #maybeWriteSquashed} to keep track of what parcelables have
+ * been seen, and what positions they were written. The value is the absolute position of
+ * each parcelable.
+ */
+ private ArrayMap<Parcelable, Integer> mWrittenSquashableParcelables;
+
+ private void ensureWrittenSquashableParcelables() {
+ if (mWrittenSquashableParcelables != null) {
+ return;
+ }
+ mWrittenSquashableParcelables = new ArrayMap<>();
+ }
+
+ private boolean mAllowSquashing = false;
+
+ /**
+ * Allow "squashing" writes in {@link #maybeWriteSquashed}. This allows subsequent calls to
+ * {@link #maybeWriteSquashed(Parcelable)} to "squash" the same instances into one in a Parcel.
+ *
+ * Typically, this method is called at the beginning of {@link Parcelable#writeToParcel}. The
+ * caller must retain the return value from this method and call {@link #restoreAllowSquashing}
+ * with it.
+ *
+ * See {@link #maybeWriteSquashed(Parcelable)} for the details.
+ *
+ * @see #restoreAllowSquashing(boolean)
+ * @see #maybeWriteSquashed(Parcelable)
+ * @see #readSquashed(SquashReadHelper)
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean allowSquashing() {
+ boolean previous = mAllowSquashing;
+ mAllowSquashing = true;
+ return previous;
+ }
+
+ /**
+ * @see #allowSquashing()
+ * @hide
+ */
+ @TestApi
+ public void restoreAllowSquashing(boolean previous) {
+ mAllowSquashing = previous;
+ if (!mAllowSquashing) {
+ mWrittenSquashableParcelables = null;
+ }
+ }
+
+ private void resetSqaushingState() {
+ if (mAllowSquashing) {
+ Slog.wtf(TAG, "allowSquashing wasn't restored.");
+ }
+ mWrittenSquashableParcelables = null;
+ mReadSquashableParcelables = null;
+ mAllowSquashing = false;
+ }
+
+ /**
+ * A map used by {@link #readSquashed} to cache parcelables. It's a map from
+ * an absolute position in a Parcel to the parcelable stored at the position.
+ */
+ private SparseArray<Parcelable> mReadSquashableParcelables;
+
+ private void ensureReadSquashableParcelables() {
+ if (mReadSquashableParcelables != null) {
+ return;
+ }
+ mReadSquashableParcelables = new SparseArray<>();
+ }
+
+ /**
+ * Write a parcelable with "squash" -- that is, when the same instance is written to the
+ * same Parcelable multiple times, instead of writing the entire instance multiple times,
+ * only write it once, and in subsequent writes we'll only write the offset to the original
+ * object.
+ *
+ * This approach does not work of the resulting Parcel is copied with {@link #appendFrom} with
+ * a non-zero offset, so we do not enable this behavior by default. Instead, we only enable
+ * it between {@link #allowSquashing} and {@link #restoreAllowSquashing}, in order to make sure
+ * we only do so within each "top level" Parcelable.
+ *
+ * Usage: Use this method in {@link Parcelable#writeToParcel}.
+ * If this method returns TRUE, it's a subsequent call, and the offset is already written,
+ * so the caller doesn't have to do anything. If this method returns FALSE, it's the first
+ * time for the instance to be written to this parcel. The caller has to proceed with its
+ * {@link Parcelable#writeToParcel}.
+ *
+ * (See {@code ApplicationInfo} for the example.)
+ *
+ * @param p the target Parcelable to write.
+ *
+ * @see #allowSquashing()
+ * @see #restoreAllowSquashing(boolean)
+ * @see #readSquashed(SquashReadHelper)
+ *
+ * @hide
+ */
+ public boolean maybeWriteSquashed(@NonNull Parcelable p) {
+ if (!mAllowSquashing) {
+ // Don't squash, and don't put it in the map either.
+ writeInt(0);
+ return false;
+ }
+ ensureWrittenSquashableParcelables();
+ final Integer firstPos = mWrittenSquashableParcelables.get(p);
+ if (firstPos != null) {
+ // Already written.
+ // Write the relative offset from the current position to the first position.
+ final int pos = dataPosition();
+
+ // We want the offset from the next byte of this integer, so we need to +4.
+ writeInt(pos - firstPos + 4);
+ return true;
+ }
+ // First time seen, write a marker.
+ writeInt(0);
+
+ // Remember the position.
+ final int pos = dataPosition();
+ mWrittenSquashableParcelables.put(p, pos);
+
+ // Return false and let the caller actually write the content.
+ return false;
+ }
+
+ /**
+ * Helper function that's used by {@link #readSquashed(SquashReadHelper)}
+ * @hide
+ */
+ public interface SquashReadHelper<T> {
+ /** Read and instantiate {@code T} from a Parcel. */
+ @NonNull
+ T readRawParceled(@NonNull Parcel p);
+ }
+
+ /**
+ * Read a {@link Parcelable} that's written with {@link #maybeWriteSquashed}.
+ *
+ * @param reader a callback function that instantiates an instance from a parcel.
+ * Typicallly, a lambda to the instructor that takes a {@link Parcel} is passed.
+ *
+ * @see #maybeWriteSquashed(Parcelable)
+ *
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T extends Parcelable> T readSquashed(SquashReadHelper<T> reader) {
+ final int offset = readInt();
+ final int pos = dataPosition();
+
+ if (offset == 0) {
+ // First time read. Unparcel, and remember it.
+ final T p = reader.readRawParceled(this);
+ ensureReadSquashableParcelables();
+ mReadSquashableParcelables.put(pos, p);
+ return p;
+ }
+ // Subsequent read.
+ final int firstAbsolutePos = pos - offset;
+
+ final Parcelable p = mReadSquashableParcelables.get(firstAbsolutePos);
+ if (p == null) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mReadSquashableParcelables.size(); i++) {
+ sb.append(mReadSquashableParcelables.keyAt(i)).append(' ');
+ }
+ Slog.wtfStack(TAG, "Map doesn't contain offset "
+ + firstAbsolutePos
+ + " : contains=" + sb.toString());
+ }
+ return (T) p;
+ }
+
+ /**
+ * 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 BadParcelableException("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) {
+ AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
+ int code = getExceptionCode(e);
+ 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;
+ writeStackTrace(e);
+ } 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;
+ }
+ }
+
+ /** @hide */
+ public static int getExceptionCode(@NonNull Throwable 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;
+ }
+ return code;
+ }
+
+ /** @hide */
+ public void writeStackTrace(@NonNull Throwable e) {
+ 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);
+ }
+
+ /**
+ * Special function for writing information at the front of the Parcel
+ * indicating that no exception occurred.
+ *
+ * @see #writeException
+ * @see #readException
+ */
+ public final void writeNoException() {
+ AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
+ // 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_STRICTMODE_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_STRICTMODE_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_NOTED_APPOPS_REPLY_HEADER) {
+ AppOpsManager.readAndLogNotedAppops(this);
+ // Read next header or real exception if there is no more header
+ code = readInt();
+ }
+
+ if (code == EX_HAS_STRICTMODE_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);
+ ExceptionUtils.appendCause(e, cause);
+ }
+ 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) {
+ Exception exception = createExceptionOrNull(code, msg);
+ return exception != null
+ ? exception
+ : new RuntimeException("Unknown exception code: " + code + " msg " + msg);
+ }
+
+ /** @hide */
+ public Exception createExceptionOrNull(int code, String msg) {
+ switch (code) {
+ case EX_PARCELABLE:
+ if (readInt() > 0) {
+ return (Exception) readParcelable(Parcelable.class.getClassLoader(), java.lang.Exception.class);
+ } 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);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * 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 readString16();
+ }
+
+ /** {@hide} */
+ public final @Nullable String readString8() {
+ return mReadWriteHelper.readString8(this);
+ }
+
+ /** {@hide} */
+ public final @Nullable String readString16() {
+ return mReadWriteHelper.readString16(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
+ */
+ public @Nullable String readStringNoHelper() {
+ return readString16NoHelper();
+ }
+
+ /** {@hide} */
+ public @Nullable String readString8NoHelper() {
+ return nativeReadString8(mNativePtr);
+ }
+
+ /** {@hide} */
+ public @Nullable String readString16NoHelper() {
+ return nativeReadString16(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() {
+ final IBinder result = nativeReadStrongBinder(mNativePtr);
+
+ // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking
+ // from the object that returned it.
+ if (result != null && hasFlags(
+ FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT | FLAG_PROPAGATE_ALLOW_BLOCKING)) {
+ Binder.allowBlocking(result);
+ }
+ return result;
+ }
+
+ /**
+ * 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().
+ *
+ * @deprecated Consider using {@link #readBundle(ClassLoader)} as stated above, in case this
+ * method is still preferred use the type-safer version {@link #readMap(Map, ClassLoader,
+ * Class, Class)} starting from Android {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) {
+ readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null);
+ }
+
+ /**
+ * Same as {@link #readMap(Map, ClassLoader)} but accepts {@code clazzKey} and
+ * {@code clazzValue} parameter as the types required for each key and value pair.
+ *
+ * @throws BadParcelableException If the item to be deserialized is not an instance of that
+ * class or any of its children class
+ */
+ public <K, V> void readMap(@NonNull Map<? super K, ? super V> outVal,
+ @Nullable ClassLoader loader, @NonNull Class<K> clazzKey,
+ @NonNull Class<V> clazzValue) {
+ Objects.requireNonNull(clazzKey);
+ Objects.requireNonNull(clazzValue);
+ readMapInternal(outVal, loader, clazzKey, clazzValue);
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Use the type-safer version {@link #readList(List, ClassLoader, Class)} starting
+ * from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the format to
+ * use {@link #readTypedList(List, Parcelable.Creator)} if possible (eg. if the items'
+ * class is final) since this is also more performant. Note that changing to the latter
+ * also requires changing the writes.
+ */
+ @Deprecated
+ public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) {
+ int N = readInt();
+ readListInternal(outVal, N, loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readList(List, ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ public <T> void readList(@NonNull List<? super T> outVal,
+ @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ int n = readInt();
+ readListInternal(outVal, n, loader, clazz);
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Consider using {@link #readBundle(ClassLoader)} as stated above, in case this
+ * method is still preferred use the type-safer version {@link #readHashMap(ClassLoader,
+ * Class, Class)} starting from Android {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public HashMap readHashMap(@Nullable ClassLoader loader) {
+ return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null);
+ }
+
+ /**
+ * Same as {@link #readHashMap(ClassLoader)} but accepts {@code clazzKey} and
+ * {@code clazzValue} parameter as the types required for each key and value pair.
+ *
+ * @throws BadParcelableException if the item to be deserialized is not an instance of that
+ * class or any of its children class
+ */
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ @Nullable
+ public <K, V> HashMap<K, V> readHashMap(@Nullable ClassLoader loader,
+ @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
+ Objects.requireNonNull(clazzKey);
+ Objects.requireNonNull(clazzValue);
+ return readHashMapInternal(loader, clazzKey, clazzValue);
+ }
+
+ /**
+ * 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.
+ * @see #writeBlob(byte[], int, int)
+ */
+ @Nullable
+ public final byte[] readBlob() {
+ return nativeReadBlob(mNativePtr);
+ }
+
+ /**
+ * Read and return a String[] object from the parcel.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ @Nullable
+ public final String[] readStringArray() {
+ return createString16Array();
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Use the type-safer version {@link #readArrayList(ClassLoader, Class)} starting
+ * from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the format to
+ * use {@link #createTypedArrayList(Parcelable.Creator)} if possible (eg. if the items'
+ * class is final) since this is also more performant. Note that changing to the latter
+ * also requires changing the writes.
+ */
+ @Deprecated
+ @Nullable
+ public ArrayList readArrayList(@Nullable ClassLoader loader) {
+ return readArrayListInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readArrayList(ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readArrayList(ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ @Nullable
+ public <T> ArrayList<T> readArrayList(@Nullable ClassLoader loader,
+ @NonNull Class<? extends T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readArrayListInternal(loader, clazz);
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Use the type-safer version {@link #readArray(ClassLoader, Class)} starting from
+ * Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the format to use
+ * {@link #createTypedArray(Parcelable.Creator)} if possible (eg. if the items' class is
+ * final) since this is also more performant. Note that changing to the latter also
+ * requires changing the writes.
+ */
+ @Deprecated
+ @Nullable
+ public Object[] readArray(@Nullable ClassLoader loader) {
+ return readArrayInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readArray(ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readArray(ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ @Nullable
+ public <T> T[] readArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readArrayInternal(loader, clazz);
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Use the type-safer version {@link #readSparseArray(ClassLoader, Class)} starting
+ * from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the format to
+ * use {@link #createTypedSparseArray(Parcelable.Creator)} if possible (eg. if the items'
+ * class is final) since this is also more performant. Note that changing to the latter
+ * also requires changing the writes.
+ */
+ @Deprecated
+ @Nullable
+ public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
+ return readSparseArrayInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readSparseArray(ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readSparseArray(ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ @Nullable
+ public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader,
+ @NonNull Class<? extends T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readSparseArrayInternal(loader, clazz);
+ }
+
+ /**
+ * 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.
+ *
+ * @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 and return a new ArrayList containing T (IInterface) objects from
+ * the parcel that was written with {@link #writeInterfaceList} at the
+ * current dataPosition(). Returns null if the
+ * previously written list object was null.
+ *
+ * @return A newly created ArrayList containing T (IInterface)
+ *
+ * @see #writeInterfaceList
+ */
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ @Nullable
+ public final <T extends IInterface> ArrayList<T> createInterfaceArrayList(
+ @NonNull Function<IBinder, T> asInterface) {
+ int N = readInt();
+ if (N < 0) {
+ return null;
+ }
+ ArrayList<T> l = new ArrayList<T>(N);
+ while (N > 0) {
+ l.add(asInterface.apply(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 into the given List items IInterface objects that were written with
+ * {@link #writeInterfaceList} at the current dataPosition().
+ *
+ * @see #writeInterfaceList
+ */
+ public final <T extends IInterface> void readInterfaceList(@NonNull List<T> list,
+ @NonNull Function<IBinder, T> asInterface) {
+ int M = list.size();
+ int N = readInt();
+ int i = 0;
+ for (; i < M && i < N; i++) {
+ list.set(i, asInterface.apply(readStrongBinder()));
+ }
+ for (; i<N; i++) {
+ list.add(asInterface.apply(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)
+ *
+ * @deprecated Use the type-safer version {@link #readParcelableList(List, ClassLoader, Class)}
+ * starting from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the
+ * format to use {@link #readTypedList(List, Parcelable.Creator)} if possible (eg. if the
+ * items' class is final) since this is also more performant. Note that changing to the
+ * latter also requires changing the writes.
+ */
+ @Deprecated
+ @NonNull
+ public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list,
+ @Nullable ClassLoader cl) {
+ return readParcelableListInternal(list, cl, /*clazz*/ null);
+ }
+
+ /**
+ * Same as {@link #readParcelableList(List, ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item.
+ *
+ * <p><b>Warning: </b> if the list contains items implementing the {@link Parcelable} interface,
+ * the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelableList(List, ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ @NonNull
+ public <T> List<T> readParcelableList(@NonNull List<T> list,
+ @Nullable ClassLoader cl, @NonNull Class<? extends T> clazz) {
+ Objects.requireNonNull(list);
+ Objects.requireNonNull(clazz);
+ return readParcelableListInternal(list, cl, clazz);
+ }
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ @NonNull
+ private <T> List<T> readParcelableListInternal(@NonNull List<T> list,
+ @Nullable ClassLoader cl, @Nullable Class<? extends T> clazz) {
+ 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) readParcelableInternal(cl, clazz));
+ }
+ for (; i < n; i++) {
+ list.add((T) readParcelableInternal(cl, clazz));
+ }
+ 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;
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array from a parcel. If you want to read Parcelable or
+ * IInterface values, use {@link #readFixedArray(Object, Parcelable.Creator)} or
+ * {@link #readFixedArray(Object, Function)}.
+ * @param val the destination array to hold the read values.
+ *
+ * @see #writeTypedArray
+ * @see #readBooleanArray
+ * @see #readByteArray
+ * @see #readCharArray
+ * @see #readIntArray
+ * @see #readLongArray
+ * @see #readFloatArray
+ * @see #readDoubleArray
+ * @see #readBinderArray
+ * @see #readInterfaceArray
+ * @see #readTypedArray
+ */
+ public <T> void readFixedArray(@NonNull T val) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (componentType == boolean.class) {
+ readBooleanArray((boolean[]) val);
+ } else if (componentType == byte.class) {
+ readByteArray((byte[]) val);
+ } else if (componentType == char.class) {
+ readCharArray((char[]) val);
+ } else if (componentType == int.class) {
+ readIntArray((int[]) val);
+ } else if (componentType == long.class) {
+ readLongArray((long[]) val);
+ } else if (componentType == float.class) {
+ readFloatArray((float[]) val);
+ } else if (componentType == double.class) {
+ readDoubleArray((double[]) val);
+ } else if (componentType == IBinder.class) {
+ readBinderArray((IBinder[]) val);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i));
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array of typed interfaces from a parcel.
+ * If you want to read Parcelable values, use
+ * {@link #readFixedArray(Object, Parcelable.Creator)}. For values of other types, use
+ * {@link #readFixedArray(Object)}.
+ * @param val the destination array to hold the read values.
+ */
+ public <T, S extends IInterface> void readFixedArray(@NonNull T val,
+ @NonNull Function<IBinder, S> asInterface) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (IInterface.class.isAssignableFrom(componentType)) {
+ readInterfaceArray((S[]) val, asInterface);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), asInterface);
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array of typed parcelables from a parcel.
+ * If you want to read IInterface values, use
+ * {@link #readFixedArray(Object, Function)}. For values of other types, use
+ * {@link #readFixedArray(Object)}.
+ * @param val the destination array to hold the read values.
+ */
+ public <T, S extends Parcelable> void readFixedArray(@NonNull T val,
+ @NonNull Parcelable.Creator<S> c) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (Parcelable.class.isAssignableFrom(componentType)) {
+ readTypedArray((S[]) val, c);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), c);
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ private void ensureClassHasExpectedDimensions(@NonNull Class<?> cls, int numDimension) {
+ if (numDimension <= 0) {
+ throw new BadParcelableException("Fixed-size array should have dimensions.");
+ }
+
+ for (int i = 0; i < numDimension; i++) {
+ if (!cls.isArray()) {
+ throw new BadParcelableException("Array has fewer dimensions than expected: "
+ + numDimension);
+ }
+ cls = cls.getComponentType();
+ }
+ if (cls.isArray()) {
+ throw new BadParcelableException("Array has more dimensions than expected: "
+ + numDimension);
+ }
+ }
+
+ /**
+ * Read and return a new multi-dimensional array from a parcel. Returns null if the
+ * previously written array object is null. If you want to read Parcelable or
+ * IInterface values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])} or
+ * {@link #createFixedArray(Class, Function, int[])}.
+ * @param cls the Class object for the target array type. (e.g. int[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ *
+ * @see #writeTypedArray
+ * @see #createBooleanArray
+ * @see #createByteArray
+ * @see #createCharArray
+ * @see #createIntArray
+ * @see #createLongArray
+ * @see #createFloatArray
+ * @see #createDoubleArray
+ * @see #createBinderArray
+ * @see #createInterfaceArray
+ * @see #createTypedArray
+ */
+ @Nullable
+ public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (componentType == boolean.class) {
+ val = (T) createBooleanArray();
+ } else if (componentType == byte.class) {
+ val = (T) createByteArray();
+ } else if (componentType == char.class) {
+ val = (T) createCharArray();
+ } else if (componentType == int.class) {
+ val = (T) createIntArray();
+ } else if (componentType == long.class) {
+ val = (T) createLongArray();
+ } else if (componentType == float.class) {
+ val = (T) createFloatArray();
+ } else if (componentType == double.class) {
+ val = (T) createDoubleArray();
+ } else if (componentType == IBinder.class) {
+ val = (T) createBinderArray();
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i));
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * Read and return a new multi-dimensional array of typed interfaces from a parcel.
+ * Returns null if the previously written array object is null. If you want to read
+ * Parcelable values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])}.
+ * For values of other types use {@link #createFixedArray(Class, int[])}.
+ * @param cls the Class object for the target array type. (e.g. IFoo[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ */
+ @Nullable
+ public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls,
+ @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (IInterface.class.isAssignableFrom(componentType)) {
+ val = (T) createInterfaceArray(n -> (S[]) Array.newInstance(componentType, n),
+ asInterface);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), asInterface);
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * Read and return a new multi-dimensional array of typed parcelables from a parcel.
+ * Returns null if the previously written array object is null. If you want to read
+ * IInterface values, use {@link #createFixedArray(Class, Function, int[])}.
+ * For values of other types use {@link #createFixedArray(Class, int[])}.
+ * @param cls the Class object for the target array type. (e.g. Foo[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ */
+ @Nullable
+ public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls,
+ @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (Parcelable.class.isAssignableFrom(componentType)) {
+ val = (T) createTypedArray(c);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), c);
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * 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) {
+ return readValue(loader, /* clazz */ null);
+ }
+
+
+ /**
+ * @see #readValue(int, ClassLoader, Class, Class[])
+ */
+ @Nullable
+ private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
+ @Nullable Class<?>... itemTypes) {
+ int type = readInt();
+ final T object;
+ if (isLengthPrefixed(type)) {
+ int length = readInt();
+ int start = dataPosition();
+ object = readValue(type, loader, clazz, itemTypes);
+ int actual = dataPosition() - start;
+ if (actual != length) {
+ Slog.wtfStack(TAG,
+ "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
+ + " consumed " + actual + " bytes, but " + length + " expected.");
+ }
+ } else {
+ object = readValue(type, loader, clazz, itemTypes);
+ }
+ return object;
+ }
+
+ /**
+ * This will return a {@link BiFunction} for length-prefixed types that deserializes the object
+ * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link
+ * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it
+ * will return the object itself.
+ *
+ * <p>After calling {@link BiFunction#apply} the parcel cursor will not change. Note that you
+ * shouldn't recycle the parcel, not at least until all objects have been retrieved. No
+ * synchronization attempts are made.
+ *
+ * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
+ * function objects are equal if either of the following is true:
+ * <ul>
+ * <li>{@link BiFunction#apply} has been called on both and both objects returned are equal.
+ * <li>{@link BiFunction#apply} hasn't been called on either one and everything below is true:
+ * <ul>
+ * <li>The {@code loader} parameters used to retrieve each are equal.
+ * <li>They both have the same type.
+ * <li>They have the same payload length.
+ * <li>Their binary content is the same.
+ * </ul>
+ * </ul>
+ *
+ * @hide
+ */
+ @Nullable
+ public Object readLazyValue(@Nullable ClassLoader loader) {
+ int start = dataPosition();
+ int type = readInt();
+ if (isLengthPrefixed(type)) {
+ int objectLength = readInt();
+ if (objectLength < 0) {
+ return null;
+ }
+ int end = MathUtils.addOrThrow(dataPosition(), objectLength);
+ int valueLength = end - start;
+ setDataPosition(end);
+ return new LazyValue(this, start, valueLength, type, loader);
+ } else {
+ return readValue(type, loader, /* clazz */ null);
+ }
+ }
+
+
+ private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> {
+ /**
+ * | 4B | 4B |
+ * mSource = Parcel{... | type | length | object | ...}
+ * a b c d
+ * length = d - c
+ * mPosition = a
+ * mLength = d - a
+ */
+ private final int mPosition;
+ private final int mLength;
+ private final int mType;
+ @Nullable private final ClassLoader mLoader;
+ @Nullable private Object mObject;
+
+ /**
+ * This goes from non-null to null once. Always check the nullability of this object before
+ * performing any operations, either involving itself or mObject since the happens-before
+ * established by this volatile will guarantee visibility of either. We can assume this
+ * parcel won't change anymore.
+ */
+ @Nullable private volatile Parcel mSource;
+
+ LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) {
+ mSource = requireNonNull(source);
+ mPosition = position;
+ mLength = length;
+ mType = type;
+ mLoader = loader;
+ }
+
+ @Override
+ public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
+ Parcel source = mSource;
+ if (source != null) {
+ synchronized (source) {
+ // Check mSource != null guarantees callers won't ever see different objects.
+ if (mSource != null) {
+ int restore = source.dataPosition();
+ try {
+ source.setDataPosition(mPosition);
+ mObject = source.readValue(mLoader, clazz, itemTypes);
+ } finally {
+ source.setDataPosition(restore);
+ }
+ mSource = null;
+ }
+ }
+ }
+ return mObject;
+ }
+
+ public void writeToParcel(Parcel out) {
+ Parcel source = mSource;
+ if (source != null) {
+ synchronized (source) {
+ if (mSource != null) {
+ out.appendFrom(source, mPosition, mLength);
+ return;
+ }
+ }
+ }
+
+ out.writeValue(mObject);
+ }
+
+ public boolean hasFileDescriptors() {
+ Parcel source = mSource;
+ if (source != null) {
+ synchronized (source) {
+ if (mSource != null) {
+ return source.hasFileDescriptors(mPosition, mLength);
+ }
+ }
+ }
+
+ return Parcel.hasFileDescriptors(mObject);
+ }
+
+ @Override
+ public String toString() {
+ return (mSource != null)
+ ? "Supplier{" + valueTypeToString(mType) + "@" + mPosition + "+" + mLength + '}'
+ : "Supplier{" + mObject + "}";
+ }
+
+ /**
+ * We're checking if the *lazy value* is equal to another one, not if the *object*
+ * represented by the lazy value is equal to the other one. So, if there are two lazy values
+ * and one of them has been deserialized but the other hasn't this will always return false.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof LazyValue)) {
+ return false;
+ }
+ LazyValue value = (LazyValue) other;
+ // Check if they are either both serialized or both deserialized.
+ Parcel source = mSource;
+ Parcel otherSource = value.mSource;
+ if ((source == null) != (otherSource == null)) {
+ return false;
+ }
+ // If both are deserialized, compare the live objects.
+ if (source == null) {
+ // Note that here it's guaranteed that both mObject references contain valid values
+ // (possibly null) since mSource will have provided the memory barrier for those and
+ // once deserialized we never go back to serialized state.
+ return Objects.equals(mObject, value.mObject);
+ }
+ // Better safely fail here since this could mean we get different objects.
+ if (!Objects.equals(mLoader, value.mLoader)) {
+ return false;
+ }
+ // Otherwise compare metadata prior to comparing payload.
+ if (mType != value.mType || mLength != value.mLength) {
+ return false;
+ }
+ // Finally we compare the payload.
+ return Parcel.compareData(source, mPosition, otherSource, value.mPosition, mLength);
+ }
+
+ @Override
+ public int hashCode() {
+ // Accessing mSource first to provide memory barrier for mObject
+ return Objects.hash(mSource == null, mObject, mLoader, mType, mLength);
+ }
+ }
+
+ /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */
+ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ // Avoids allocating Class[0] array
+ return readValue(type, loader, clazz, (Class<?>[]) null);
+ }
+
+ /**
+ * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
+ * type first.
+ *
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ * @param itemTypes If the value is a container, these represent the item types (eg. for a list
+ * it's the item type, for a map, it's the key type, followed by the value
+ * type).
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz,
+ @Nullable Class<?>... itemTypes) {
+ final Object object;
+ switch (type) {
+ case VAL_NULL:
+ object = null;
+ break;
+
+ case VAL_STRING:
+ object = readString();
+ break;
+
+ case VAL_INTEGER:
+ object = readInt();
+ break;
+
+ case VAL_MAP:
+ checkTypeToUnparcel(clazz, HashMap.class);
+ Class<?> keyType = ArrayUtils.getOrNull(itemTypes, 0);
+ Class<?> valueType = ArrayUtils.getOrNull(itemTypes, 1);
+ checkArgument((keyType == null) == (valueType == null));
+ object = readHashMapInternal(loader, keyType, valueType);
+ break;
+
+ case VAL_PARCELABLE:
+ object = readParcelableInternal(loader, clazz);
+ break;
+
+ case VAL_SHORT:
+ object = (short) readInt();
+ break;
+
+ case VAL_LONG:
+ object = readLong();
+ break;
+
+ case VAL_FLOAT:
+ object = readFloat();
+ break;
+
+ case VAL_DOUBLE:
+ object = readDouble();
+ break;
+
+ case VAL_BOOLEAN:
+ object = readInt() == 1;
+ break;
+
+ case VAL_CHARSEQUENCE:
+ object = readCharSequence();
+ break;
+
+ case VAL_LIST: {
+ checkTypeToUnparcel(clazz, ArrayList.class);
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ object = readArrayListInternal(loader, itemType);
+ break;
+ }
+ case VAL_BOOLEANARRAY:
+ object = createBooleanArray();
+ break;
+
+ case VAL_BYTEARRAY:
+ object = createByteArray();
+ break;
+
+ case VAL_STRINGARRAY:
+ object = readStringArray();
+ break;
+
+ case VAL_CHARSEQUENCEARRAY:
+ object = readCharSequenceArray();
+ break;
+
+ case VAL_IBINDER:
+ object = readStrongBinder();
+ break;
+
+ case VAL_OBJECTARRAY: {
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class);
+ object = readArrayInternal(loader, itemType);
+ break;
+ }
+ case VAL_INTARRAY:
+ object = createIntArray();
+ break;
+
+ case VAL_LONGARRAY:
+ object = createLongArray();
+ break;
+
+ case VAL_BYTE:
+ object = readByte();
+ break;
+
+ case VAL_SERIALIZABLE:
+ object = readSerializableInternal(loader, clazz);
+ break;
+
+ case VAL_PARCELABLEARRAY: {
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class);
+ object = readParcelableArrayInternal(loader, itemType);
+ break;
+ }
+ case VAL_SPARSEARRAY: {
+ checkTypeToUnparcel(clazz, SparseArray.class);
+ Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+ object = readSparseArrayInternal(loader, itemType);
+ break;
+ }
+ case VAL_SPARSEBOOLEANARRAY:
+ object = readSparseBooleanArray();
+ break;
+
+ case VAL_BUNDLE:
+ object = readBundle(loader); // loading will be deferred
+ break;
+
+ case VAL_PERSISTABLEBUNDLE:
+ object = readPersistableBundle(loader);
+ break;
+
+ case VAL_SIZE:
+ object = readSize();
+ break;
+
+ case VAL_SIZEF:
+ object = readSizeF();
+ break;
+
+ case VAL_DOUBLEARRAY:
+ object = createDoubleArray();
+ break;
+
+ case VAL_CHAR:
+ object = (char) readInt();
+ break;
+
+ case VAL_SHORTARRAY:
+ object = createShortArray();
+ break;
+
+ case VAL_CHARARRAY:
+ object = createCharArray();
+ break;
+
+ case VAL_FLOATARRAY:
+ object = createFloatArray();
+ break;
+
+ default:
+ int off = dataPosition() - 4;
+ throw new BadParcelableException(
+ "Parcel " + this + ": Unmarshalling unknown type code " + type
+ + " at offset " + off);
+ }
+ if (object != null && clazz != null && !clazz.isInstance(object)) {
+ throw new BadTypeParcelableException("Unparcelled object " + object
+ + " is not an instance of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ return (T) object;
+ }
+
+ private boolean isLengthPrefixed(int type) {
+ // In general, we want custom types and containers of custom types to be length-prefixed,
+ // this allows clients (eg. Bundle) to skip their content during deserialization. The
+ // exception to this is Bundle, since Bundle is already length-prefixed and already copies
+ // the correspondent section of the parcel internally.
+ switch (type) {
+ case VAL_MAP:
+ case VAL_PARCELABLE:
+ case VAL_LIST:
+ case VAL_SPARSEARRAY:
+ case VAL_PARCELABLEARRAY:
+ case VAL_OBJECTARRAY:
+ case VAL_SERIALIZABLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of
+ * {@code requiredArrayType}.
+ */
+ private void checkArrayTypeToUnparcel(@Nullable Class<?> requiredArrayType,
+ Class<?> componentTypeToUnparcel) {
+ if (requiredArrayType != null) {
+ // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check
+ Class<?> requiredComponentType = requiredArrayType.getComponentType();
+ if (requiredComponentType == null) {
+ throw new BadTypeParcelableException(
+ "About to unparcel an array but type "
+ + requiredArrayType.getCanonicalName()
+ + " required by caller is not an array.");
+ }
+ checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel);
+ }
+ }
+
+ /**
+ * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code
+ * requiredType} is not {@code null}.
+ */
+ private void checkTypeToUnparcel(@Nullable Class<?> requiredType, Class<?> typeToUnparcel) {
+ if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) {
+ throw new BadTypeParcelableException(
+ "About to unparcel a " + typeToUnparcel.getCanonicalName()
+ + ", which is not a subtype of type " + requiredType.getCanonicalName()
+ + " required by caller.");
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Use the type-safer version {@link #readParcelable(ClassLoader, Class)} starting
+ * from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the format to
+ * use {@link Parcelable.Creator#createFromParcel(Parcel)} if possible since this is also
+ * more performant. Note that changing to the latter also requires changing the writes.
+ */
+ @Deprecated
+ @Nullable
+ public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
+ return readParcelableInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
+ * required for each item.
+ *
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelable(ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ @Nullable
+ public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readParcelableInternal(loader, clazz);
+ }
+
+ /**
+ * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
+ 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);
+ }
+
+ /**
+ * Read and return a Parcelable.Creator from the parcel. The given class loader will be used to
+ * load the {@link Parcelable.Creator}. If it is null, the default class loader will be used.
+ *
+ * @param loader A ClassLoader from which to instantiate the {@link Parcelable.Creator}
+ * object, or null for the default class loader.
+ * @return the previously written {@link Parcelable.Creator}, or null if a null Creator was
+ * written.
+ * @throws BadParcelableException Throws BadParcelableException if there was an error trying to
+ * read the {@link Parcelable.Creator}.
+ *
+ * @see #writeParcelableCreator
+ *
+ * @deprecated Use the type-safer version {@link #readParcelableCreator(ClassLoader, Class)}
+ * starting from Android {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
+ return readParcelableCreatorInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
+ * as the required type.
+ *
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelableCreator(ClassLoader) instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there there was an error
+ * trying to read the {@link Parcelable.Creator}.
+ */
+ @Nullable
+ public <T> Parcelable.Creator<T> readParcelableCreator(
+ @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readParcelableCreatorInternal(loader, clazz);
+ }
+
+ /**
+ * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> Parcelable.Creator<T> readParcelableCreatorInternal(
+ @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ String name = readString();
+ if (name == null) {
+ return null;
+ }
+
+ Pair<Parcelable.Creator<?>, Class<?>> creatorAndParcelableClass;
+ synchronized (sPairedCreators) {
+ HashMap<String, Pair<Parcelable.Creator<?>, Class<?>>> map =
+ sPairedCreators.get(loader);
+ if (map == null) {
+ sPairedCreators.put(loader, new HashMap<>());
+ mCreators.put(loader, new HashMap<>());
+ creatorAndParcelableClass = null;
+ } else {
+ creatorAndParcelableClass = map.get(name);
+ }
+ }
+
+ if (creatorAndParcelableClass != null) {
+ Parcelable.Creator<?> creator = creatorAndParcelableClass.first;
+ Class<?> parcelableClass = creatorAndParcelableClass.second;
+ if (clazz != null) {
+ if (!clazz.isAssignableFrom(parcelableClass)) {
+ throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
+ + "a subclass of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ }
+
+ return (Parcelable.Creator<T>) creator;
+ }
+
+ Parcelable.Creator<?> creator;
+ Class<?> parcelableClass;
+ 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.
+ parcelableClass = Class.forName(name, false /* initialize */,
+ parcelableClassLoader);
+ if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
+ throw new BadParcelableException("Parcelable protocol requires subclassing "
+ + "from Parcelable on class " + name);
+ }
+ if (clazz != null) {
+ if (!clazz.isAssignableFrom(parcelableClass)) {
+ throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
+ + "a subclass of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ }
+
+ 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, e);
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "Class not found when unmarshalling: " + name, e);
+ throw new BadParcelableException(
+ "ClassNotFoundException when unmarshalling: " + name, e);
+ } catch (NoSuchFieldException e) {
+ throw new BadParcelableException("Parcelable protocol requires a "
+ + "Parcelable.Creator object called "
+ + "CREATOR on class " + name, e);
+ }
+ if (creator == null) {
+ throw new BadParcelableException("Parcelable protocol requires a "
+ + "non-null Parcelable.Creator object called "
+ + "CREATOR on class " + name);
+ }
+
+ synchronized (sPairedCreators) {
+ sPairedCreators.get(loader).put(name, Pair.create(creator, parcelableClass));
+ mCreators.get(loader).put(name, creator);
+ }
+
+ return (Parcelable.Creator<T>) 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
+ *
+ * @deprecated Use the type-safer version {@link #readParcelableArray(ClassLoader, Class)}
+ * starting from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the
+ * format to use {@link #createTypedArray(Parcelable.Creator)} if possible (eg. if the
+ * items' class is final) since this is also more performant. Note that changing to the
+ * latter also requires changing the writes.
+ */
+ @Deprecated
+ @Nullable
+ public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
+ return readParcelableArrayInternal(loader, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readParcelableArray(ClassLoader)} but accepts {@code clazz} parameter as
+ * the type required for each item.
+ *
+ * <p><b>Warning: </b> the class that implements {@link Parcelable} has to be the immediately
+ * enclosing class of the runtime type of its CREATOR field (that is,
+ * {@link Class#getEnclosingClass()} has to return the parcelable implementing class),
+ * otherwise this method might throw an exception. If the Parcelable class does not enclose the
+ * CREATOR, use the deprecated {@link #readParcelableArray(ClassLoader)} instead.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children classes or there was an error
+ * trying to instantiate an element.
+ */
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ @Nullable
+ public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ return readParcelableArrayInternal(loader, requireNonNull(clazz));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> T[] readParcelableArrayInternal(@Nullable ClassLoader loader,
+ @Nullable Class<T> clazz) {
+ int n = readInt();
+ if (n < 0) {
+ return null;
+ }
+ T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n));
+ for (int i = 0; i < n; i++) {
+ p[i] = readParcelableInternal(loader, clazz);
+ }
+ 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.
+ *
+ * Unlike {@link #readSerializable(ClassLoader, Class)}, it uses the nearest valid class loader
+ * up the execution stack to instantiate the Serializable object.
+ *
+ * @deprecated Use the type-safer version {@link #readSerializable(ClassLoader, Class)} starting
+ * from Android {@link Build.VERSION_CODES#TIRAMISU}.
+ */
+ @Deprecated
+ @Nullable
+ public Serializable readSerializable() {
+ return readSerializableInternal(/* loader */ null, /* clazz */ null);
+ }
+
+ /**
+ * Same as {@link #readSerializable()} but accepts {@code loader} and {@code clazz} parameters.
+ *
+ * @param loader A ClassLoader from which to instantiate the Serializable object,
+ * or null for the default class loader.
+ * @param clazz The type of the object expected.
+ *
+ * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+ * is not an instance of that class or any of its children class or there there was an error
+ * deserializing the object.
+ */
+ @Nullable
+ public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ Objects.requireNonNull(clazz);
+ return readSerializableInternal(
+ loader == null ? getClass().getClassLoader() : loader, clazz);
+ }
+
+ /**
+ * @param clazz The type of the serializable expected or {@code null} for performing no checks
+ */
+ @Nullable
+ private <T> T readSerializableInternal(@Nullable final ClassLoader loader,
+ @Nullable Class<T> clazz) {
+ 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;
+ }
+
+ try {
+ if (clazz != null && loader != null) {
+ // If custom classloader is provided, resolve the type of serializable using the
+ // name, then check the type before deserialization. As in this case we can resolve
+ // the class the same way as ObjectInputStream, using the provided classloader.
+ Class<?> cl = Class.forName(name, false, loader);
+ if (!clazz.isAssignableFrom(cl)) {
+ throw new BadTypeParcelableException("Serializable object "
+ + cl.getName() + " is not a subclass of required class "
+ + clazz.getName() + " provided in the parameter");
+ }
+ }
+ byte[] serializedData = createByteArray();
+ ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
+ 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);
+ return Objects.requireNonNull(c);
+ }
+ return super.resolveClass(osClass);
+ }
+ };
+ T object = (T) ois.readObject();
+ if (clazz != null && loader == null) {
+ // If custom classloader is not provided, check the type of the serializable using
+ // the deserialized object, as we cannot resolve the class the same way as
+ // ObjectInputStream.
+ if (!clazz.isAssignableFrom(object.getClass())) {
+ throw new BadTypeParcelableException("Serializable object "
+ + object.getClass().getName() + " is not a subclass of required class "
+ + clazz.getName() + " provided in the parameter");
+ }
+ }
+ return object;
+ } catch (IOException ioe) {
+ throw new BadParcelableException("Parcelable encountered "
+ + "IOException reading a Serializable object (name = "
+ + name + ")", ioe);
+ } catch (ClassNotFoundException cnfe) {
+ throw new BadParcelableException("Parcelable encountered "
+ + "ClassNotFoundException reading a Serializable object (name = "
+ + name + ")", cnfe);
+ }
+ }
+
+
+ // Left due to the UnsupportedAppUsage. Do not use anymore - use sPairedCreators instead
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static final HashMap<ClassLoader, HashMap<String, Parcelable.Creator<?>>>
+ mCreators = new HashMap<>();
+
+ // Cache of previously looked up CREATOR.createFromParcel() methods for particular classes.
+ // Keys are the names of the classes, values are a pair consisting of a parcelable creator,
+ // and the class of the parcelable type for the object.
+ private static final HashMap<ClassLoader, HashMap<String,
+ Pair<Parcelable.Creator<?>, Class<?>>>> sPairedCreators = 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) {
+ Parcel res = null;
+ synchronized (sPoolSync) {
+ if (sHolderPool != null) {
+ res = sHolderPool;
+ sHolderPool = res.mPoolNext;
+ res.mPoolNext = null;
+ sHolderPoolSize--;
+ }
+ }
+
+ // When no cache found above, create from scratch; otherwise prepare the
+ // cached object to be used
+ if (res == null) {
+ res = new Parcel(obj);
+ } else {
+ res.mRecycled = false;
+ if (DEBUG_RECYCLE) {
+ res.mStack = new RuntimeException();
+ }
+ res.init(obj);
+ }
+ return res;
+ }
+
+ 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() {
+ mFlags = 0;
+ resetSqaushingState();
+ if (mOwnsNativeParcelObject) {
+ nativeFreeBuffer(mNativePtr);
+ }
+ mReadWriteHelper = ReadWriteHelper.DEFAULT;
+ }
+
+ private void destroy() {
+ resetSqaushingState();
+ if (mNativePtr != 0) {
+ if (mOwnsNativeParcelObject) {
+ nativeDestroy(mNativePtr);
+ }
+ mNativePtr = 0;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (DEBUG_RECYCLE) {
+ // we could always have this log on, but it's spammy
+ if (!mRecycled) {
+ Log.w(TAG, "Client did not call Parcel.recycle()", mStack);
+ }
+ }
+ destroy();
+ }
+
+ /**
+ * To be replaced by {@link #readMapInternal(Map, int, ClassLoader, Class, Class)}, but keep
+ * the old API for compatibility usages.
+ */
+ /* package */ void readMapInternal(@NonNull Map outVal, int n,
+ @Nullable ClassLoader loader) {
+ readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null);
+ }
+
+ @Nullable
+ private <K, V> HashMap<K, V> readHashMapInternal(@Nullable ClassLoader loader,
+ @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
+ int n = readInt();
+ if (n < 0) {
+ return null;
+ }
+ HashMap<K, V> map = new HashMap<>(n);
+ readMapInternal(map, n, loader, clazzKey, clazzValue);
+ return map;
+ }
+
+ private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal,
+ @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
+ @Nullable Class<V> clazzValue) {
+ int n = readInt();
+ readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+ }
+
+ private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
+ @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
+ @Nullable Class<V> clazzValue) {
+ while (n > 0) {
+ K key = readValue(loader, clazzKey);
+ V value = readValue(loader, clazzValue);
+ outVal.put(key, value);
+ n--;
+ }
+ }
+
+ private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
+ int size, @Nullable ClassLoader loader) {
+ readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
+ }
+
+ /**
+ * Reads a map into {@code map}.
+ *
+ * @param sorted Whether the keys are sorted by their hashes, if so we use an optimized path.
+ * @param lazy Whether to populate the map with lazy {@link Function} objects for
+ * length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more
+ * details.
+ * @return a count of the lazy values in the map
+ * @hide
+ */
+ int readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
+ boolean lazy, @Nullable ClassLoader loader) {
+ int lazyValues = 0;
+ while (size > 0) {
+ String key = readString();
+ Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
+ if (value instanceof LazyValue) {
+ lazyValues++;
+ }
+ if (sorted) {
+ map.append(key, value);
+ } else {
+ map.put(key, value);
+ }
+ size--;
+ }
+ if (sorted) {
+ map.validate();
+ }
+ return lazyValues;
+ }
+
+ /**
+ * @hide For testing only.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void readArrayMap(@NonNull ArrayMap<? super String, Object> 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;
+ }
+
+ /**
+ * The method is replaced by {@link #readListInternal(List, int, ClassLoader, Class)}, however
+ * we are keeping this unused method here to allow unsupported app usages.
+ */
+ private void readListInternal(@NonNull List outVal, int n, @Nullable ClassLoader loader) {
+ readListInternal(outVal, n, loader, /* clazz */ null);
+ }
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ private <T> void readListInternal(@NonNull List<? super T> outVal, int n,
+ @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ while (n > 0) {
+ T value = readValue(loader, clazz);
+ //Log.d(TAG, "Unmarshalling value=" + value);
+ outVal.add(value);
+ n--;
+ }
+ }
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ @Nullable
+ private <T> ArrayList<T> readArrayListInternal(@Nullable ClassLoader loader,
+ @Nullable Class<? extends T> clazz) {
+ int n = readInt();
+ if (n < 0) {
+ return null;
+ }
+ ArrayList<T> l = new ArrayList<>(n);
+ readListInternal(l, n, loader, clazz);
+ return l;
+ }
+
+ /**
+ * The method is replaced by {@link #readArrayInternal(ClassLoader, Class)}, however
+ * we are keeping this unused method here to allow unsupported app usages.
+ */
+ private void readArrayInternal(@NonNull Object[] outVal, int N,
+ @Nullable ClassLoader loader) {
+ for (int i = 0; i < N; i++) {
+ Object value = readValue(loader, /* clazz */ null);
+ outVal[i] = value;
+ }
+ }
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private <T> T[] readArrayInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+ int n = readInt();
+ if (n < 0) {
+ return null;
+ }
+ T[] outVal = (T[]) ((clazz == null) ? new Object[n] : Array.newInstance(clazz, n));
+
+ for (int i = 0; i < n; i++) {
+ T value = readValue(loader, clazz);
+ outVal[i] = value;
+ }
+ return outVal;
+ }
+
+ /**
+ * The method is replaced by {@link #readSparseArray(ClassLoader, Class)}, however
+ * we are keeping this unused method here to allow unsupported app usages.
+ */
+ private void readSparseArrayInternal(@NonNull SparseArray outVal, int N,
+ @Nullable ClassLoader loader) {
+ while (N > 0) {
+ int key = readInt();
+ Object value = readValue(loader);
+ outVal.append(key, value);
+ N--;
+ }
+ }
+
+ /**
+ * @param clazz The type of the object expected or {@code null} for performing no checks.
+ */
+ @Nullable
+ private <T> SparseArray<T> readSparseArrayInternal(@Nullable ClassLoader loader,
+ @Nullable Class<? extends T> clazz) {
+ int n = readInt();
+ if (n < 0) {
+ return null;
+ }
+ SparseArray<T> outVal = new SparseArray<>(n);
+
+ while (n > 0) {
+ int key = readInt();
+ T value = readValue(loader, clazz);
+ outVal.append(key, value);
+ n--;
+ }
+ return outVal;
+ }
+
+
+ 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 getOpenAshmemSize() {
+ return nativeGetOpenAshmemSize(mNativePtr);
+ }
+
+ private static String valueTypeToString(int type) {
+ switch (type) {
+ case VAL_NULL: return "VAL_NULL";
+ case VAL_INTEGER: return "VAL_INTEGER";
+ case VAL_MAP: return "VAL_MAP";
+ case VAL_BUNDLE: return "VAL_BUNDLE";
+ case VAL_PERSISTABLEBUNDLE: return "VAL_PERSISTABLEBUNDLE";
+ case VAL_PARCELABLE: return "VAL_PARCELABLE";
+ case VAL_SHORT: return "VAL_SHORT";
+ case VAL_LONG: return "VAL_LONG";
+ case VAL_FLOAT: return "VAL_FLOAT";
+ case VAL_DOUBLE: return "VAL_DOUBLE";
+ case VAL_BOOLEAN: return "VAL_BOOLEAN";
+ case VAL_CHARSEQUENCE: return "VAL_CHARSEQUENCE";
+ case VAL_LIST: return "VAL_LIST";
+ case VAL_SPARSEARRAY: return "VAL_SPARSEARRAY";
+ case VAL_BOOLEANARRAY: return "VAL_BOOLEANARRAY";
+ case VAL_BYTEARRAY: return "VAL_BYTEARRAY";
+ case VAL_STRINGARRAY: return "VAL_STRINGARRAY";
+ case VAL_CHARSEQUENCEARRAY: return "VAL_CHARSEQUENCEARRAY";
+ case VAL_IBINDER: return "VAL_IBINDER";
+ case VAL_PARCELABLEARRAY: return "VAL_PARCELABLEARRAY";
+ case VAL_INTARRAY: return "VAL_INTARRAY";
+ case VAL_LONGARRAY: return "VAL_LONGARRAY";
+ case VAL_BYTE: return "VAL_BYTE";
+ case VAL_SIZE: return "VAL_SIZE";
+ case VAL_SIZEF: return "VAL_SIZEF";
+ case VAL_DOUBLEARRAY: return "VAL_DOUBLEARRAY";
+ case VAL_CHAR: return "VAL_CHAR";
+ case VAL_SHORTARRAY: return "VAL_SHORTARRAY";
+ case VAL_CHARARRAY: return "VAL_CHARARRAY";
+ case VAL_FLOATARRAY: return "VAL_FLOATARRAY";
+ case VAL_OBJECTARRAY: return "VAL_OBJECTARRAY";
+ case VAL_SERIALIZABLE: return "VAL_SERIALIZABLE";
+ default: return "UNKNOWN(" + type + ")";
+ }
+ }
+}
diff --git a/android-34/android/os/ParcelArrayPerfTest.java b/android-34/android/os/ParcelArrayPerfTest.java
new file mode 100644
index 0000000..af6d6b0
--- /dev/null
+++ b/android-34/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-34/android/os/ParcelDuration.java b/android-34/android/os/ParcelDuration.java
new file mode 100644
index 0000000..37cde31
--- /dev/null
+++ b/android-34/android/os/ParcelDuration.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.time.Duration;
+
+/**
+ * Parcelable version of {@link Duration} that can be used in binder calls.
+ *
+ * @hide
+ */
+public final class ParcelDuration implements Parcelable {
+
+ private final long mSeconds;
+ private final int mNanos;
+
+ /**
+ * Construct a Duration object using the given millisecond value.
+ *
+ * @hide
+ */
+ public ParcelDuration(long ms) {
+ this(Duration.ofMillis(ms));
+ }
+
+ /**
+ * Wrap a {@link Duration} instance.
+ *
+ * @param duration The {@link Duration} instance to wrap.
+ */
+ public ParcelDuration(@NonNull Duration duration) {
+ mSeconds = duration.getSeconds();
+ mNanos = duration.getNano();
+ }
+
+ private ParcelDuration(@NonNull Parcel parcel) {
+ mSeconds = parcel.readLong();
+ mNanos = parcel.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeLong(mSeconds);
+ parcel.writeInt(mNanos);
+ }
+
+ /**
+ * Returns a {@link Duration} instance that's equivalent to this Duration's length.
+ *
+ * @return a {@link Duration} instance of identical length.
+ */
+ @NonNull
+ public Duration getDuration() {
+ return Duration.ofSeconds(mSeconds, mNanos);
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return getDuration().toString();
+ }
+
+ /**
+ * Creator for Duration.
+ */
+ @NonNull
+ public static final Parcelable.Creator<ParcelDuration> CREATOR =
+ new Parcelable.Creator<ParcelDuration>() {
+
+ @Override
+ @NonNull
+ public ParcelDuration createFromParcel(@NonNull Parcel source) {
+ return new ParcelDuration(source);
+ }
+
+ @Override
+ @NonNull
+ public ParcelDuration[] newArray(int size) {
+ return new ParcelDuration[size];
+ }
+ };
+}
diff --git a/android-34/android/os/ParcelFileDescriptor.java b/android-34/android/os/ParcelFileDescriptor.java
new file mode 100644
index 0000000..93d5082
--- /dev/null
+++ b/android-34/android/os/ParcelFileDescriptor.java
@@ -0,0 +1,1242 @@
+/*
+ * 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.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
+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 android.util.Slog;
+
+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}.
+ * mClosed is always true if mWrapped is non-null.
+ */
+ 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.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
+ *
+ * @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.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
+ *
+ * @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)
+ */
+ // We can't accept a generic Executor here, since we need to use
+ // MessageQueue.addOnFileDescriptorEventListener()
+ @SuppressLint("ExecutorRegistration")
+ 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);
+ }
+
+ /**
+ * Create a new ParcelFileDescriptor wrapping an already-opened file.
+ *
+ * @param pfd The already-opened file.
+ * @param handler to call listener from.
+ * @param listener to be invoked when the returned descriptor has been
+ * closed.
+ * @return a new ParcelFileDescriptor pointing to the given file.
+ */
+ // We can't accept a generic Executor here, since we need to use
+ // MessageQueue.addOnFileDescriptorEventListener()
+ @SuppressLint("ExecutorRegistration")
+ public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
+ @NonNull Handler handler, @NonNull 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 {
+ if ((mode & MODE_WRITE_ONLY) != 0 && (mode & MODE_APPEND) == 0
+ && (mode & MODE_TRUNCATE) == 0 && ((mode & MODE_READ_ONLY) == 0)
+ && file != null && file.exists()) {
+ Slog.wtfQuiet(TAG, "ParcelFileDescriptor.open is called with w without t or a or r, "
+ + "which will have a different behavior beginning in Android Q."
+ + "\nMode: " + mode + "\nFilename: " + file.getPath());
+ }
+
+ 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);
+ try {
+ if (data.length > 0) {
+ file.writeBytes(data, 0, 0, data.length);
+ }
+ file.deactivate();
+ FileDescriptor fd = file.getFileDescriptor();
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+ } finally {
+ file.close();
+ }
+ }
+
+ /**
+ * Converts a string representing a file mode, such as "rw", into a bitmask suitable for use
+ * with {@link #open}.
+ * <p>
+ * The argument must define at least one of the following base access modes:
+ * <ul>
+ * <li>"r" indicates the file should be opened in read-only mode, equivalent
+ * to {@link OsConstants#O_RDONLY}.
+ * <li>"w" indicates the file should be opened in write-only mode,
+ * equivalent to {@link OsConstants#O_WRONLY}.
+ * <li>"rw" indicates the file should be opened in read-write mode,
+ * equivalent to {@link OsConstants#O_RDWR}.
+ * </ul>
+ * In addition to a base access mode, the following additional modes may
+ * requested:
+ * <ul>
+ * <li>"a" indicates the file should be opened in append mode, equivalent to
+ * {@link OsConstants#O_APPEND}. Before each write, the file offset is
+ * positioned at the end of the file.
+ * <li>"t" indicates the file should be opened in truncate mode, equivalent
+ * to {@link OsConstants#O_TRUNC}. If the file already exists and is a
+ * regular file and is opened for writing, it will be truncated to length 0.
+ * </ul>
+ *
+ * @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)
+ || OsConstants.S_ISCHR(Os.stat(path).st_mode)) {
+ return new File(path);
+ } else {
+ throw new IOException("Not a regular file or character device: " + 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) {
+ // mWrapped was and is null.
+ 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-34/android/os/ParcelFormatException.java b/android-34/android/os/ParcelFormatException.java
new file mode 100644
index 0000000..8b6fda0
--- /dev/null
+++ b/android-34/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-34/android/os/ParcelObtainPerfTest.java b/android-34/android/os/ParcelObtainPerfTest.java
new file mode 100644
index 0000000..760ae12
--- /dev/null
+++ b/android-34/android/os/ParcelObtainPerfTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ParcelObtainPerfTest {
+ private static final int ITERATIONS = 1_000_000;
+
+ @Test
+ public void timeContention_01() throws Exception {
+ timeContention(1);
+ }
+
+ @Test
+ public void timeContention_04() throws Exception {
+ timeContention(4);
+ }
+
+ @Test
+ public void timeContention_16() throws Exception {
+ timeContention(16);
+ }
+
+ private static void timeContention(int numThreads) throws Exception {
+ final long start = SystemClock.elapsedRealtime();
+ {
+ final ObtainThread[] threads = new ObtainThread[numThreads];
+ for (int i = 0; i < numThreads; i++) {
+ final ObtainThread thread = new ObtainThread(ITERATIONS / numThreads);
+ thread.start();
+ threads[i] = thread;
+ }
+ for (int i = 0; i < numThreads; i++) {
+ threads[i].join();
+ }
+ }
+ final long duration = SystemClock.elapsedRealtime() - start;
+
+ final Bundle results = new Bundle();
+ results.putLong("duration", duration);
+ InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
+ }
+
+ public static class ObtainThread extends Thread {
+ public int iterations;
+
+ public ObtainThread(int iterations) {
+ this.iterations = iterations;
+ }
+
+ @Override
+ public void run() {
+ while (iterations-- > 0) {
+ final Parcel data = Parcel.obtain();
+ final Parcel reply = Parcel.obtain();
+ try {
+ data.writeInt(32);
+ reply.writeInt(32);
+ } finally {
+ reply.recycle();
+ data.recycle();
+ }
+ }
+ }
+ }
+}
diff --git a/android-34/android/os/ParcelPerfTest.java b/android-34/android/os/ParcelPerfTest.java
new file mode 100644
index 0000000..be2f9d7
--- /dev/null
+++ b/android-34/android/os/ParcelPerfTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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 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-34/android/os/ParcelStringPerfTest.java b/android-34/android/os/ParcelStringPerfTest.java
new file mode 100644
index 0000000..2b861cb
--- /dev/null
+++ b/android-34/android/os/ParcelStringPerfTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class ParcelStringPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameterized.Parameter(0)
+ public String mName;
+ @Parameterized.Parameter(1)
+ public String mValue;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> getParameters() {
+ return Arrays.asList(new Object[][] {
+ { "simple", "com.example.typical_package_name" },
+ { "complex", "從不喜歡孤單一個 - 蘇永康/吳雨霏" },
+ });
+ }
+
+ @Test
+ public void timeWriteString8() {
+ final Parcel parcel = Parcel.obtain();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ parcel.setDataPosition(0);
+ parcel.writeString8(mValue);
+ }
+ }
+
+ @Test
+ public void timeWriteString16() {
+ final Parcel parcel = Parcel.obtain();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ parcel.setDataPosition(0);
+ parcel.writeString16(mValue);
+ }
+ }
+}
diff --git a/android-34/android/os/ParcelUuid.java b/android-34/android/os/ParcelUuid.java
new file mode 100644
index 0000000..b529694
--- /dev/null
+++ b/android-34/android/os/ParcelUuid.java
@@ -0,0 +1,136 @@
+/*
+ * 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.Nullable;
+import android.compat.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(@Nullable 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-34/android/os/Parcelable.java b/android-34/android/os/Parcelable.java
new file mode 100644
index 0000000..f2b60a4
--- /dev/null
+++ b/android-34/android/os/Parcelable.java
@@ -0,0 +1,248 @@
+/*
+ * 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.SystemApi;
+
+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 public static field called
+ * <code>CREATOR</code> of a type that implements the {@link Parcelable.Creator}
+ * interface.
+ *
+ * <p>A typical implementation of Parcelable is:</p>
+ *
+ * <div>
+ * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
+ * <pre class="prettyprint lang-kotlin">
+ * class MyParcelable private constructor(`in`: Parcel) : Parcelable {
+ * private val mData: Int = `in`.readInt()
+ *
+ * override fun describeContents(): Int {
+ * return 0
+ * }
+ *
+ * override fun writeToParcel(out: Parcel, flags: Int) {
+ * out.writeInt(mData)
+ * }
+ *
+ * companion object CREATOR: Parcelable.Creator<MyParcelable?> {
+ * override fun createFromParcel(`in`: Parcel): MyParcelable? {
+ * return MyParcelable(`in`)
+ * }
+ *
+ * override fun newArray(size: Int): Array<MyParcelable?> {
+ * return arrayOfNulls(size)
+ * }
+ * }
+ * }
+ * </pre>
+ * </section><section><h3 id="java">Java</h3>
+ * <pre class="prettyprint lang-java">
+ * 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></section></div></div>
+ */
+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 {}
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PARCELABLE_STABILITY_" }, value = {
+ PARCELABLE_STABILITY_LOCAL,
+ PARCELABLE_STABILITY_VINTF,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Stability {}
+
+ /**
+ * Something that is not meant to cross compilation boundaries.
+ *
+ * Note: unlike binder/Stability.h which uses bitsets to detect stability,
+ * since we don't currently have a notion of different local locations,
+ * higher stability levels are formed at higher levels.
+ *
+ * For instance, contained entirely within system partitions.
+ * @see #getStability()
+ * @see ParcelableHolder
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public static final int PARCELABLE_STABILITY_LOCAL = 0x0000;
+ /**
+ * Something that is meant to be used between system and vendor.
+ * @see #getStability()
+ * @see ParcelableHolder
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public static final int PARCELABLE_STABILITY_VINTF = 0x0001;
+
+ /**
+ * 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();
+
+ /**
+ * 'Stable' means this parcelable is guaranteed to be stable for multiple years.
+ * It must be guaranteed by setting stability field in aidl_interface,
+ * OR explicitly override this method from @JavaOnlyStableParcelable marked Parcelable.
+ * WARNING: isStable() is only expected to be overridden by auto-generated code,
+ * OR @JavaOnlyStableParcelable marked Parcelable only if there is guaranteed to
+ * be only once copy of the parcelable on the system.
+ * @return true if this parcelable is stable.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ default @Stability int getStability() {
+ return PARCELABLE_STABILITY_LOCAL;
+ }
+
+ /**
+ * 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(@NonNull 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-34/android/os/ParcelableException.java b/android-34/android/os/ParcelableException.java
new file mode 100644
index 0000000..81b9d15
--- /dev/null
+++ b/android-34/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-34/android/os/ParcelableHolder.java b/android-34/android/os/ParcelableHolder.java
new file mode 100644
index 0000000..a739ba3
--- /dev/null
+++ b/android-34/android/os/ParcelableHolder.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.MathUtils;
+
+/**
+ * ParcelableHolder is a Parcelable which can contain another Parcelable.
+ * The main use case of ParcelableHolder is to make a Parcelable extensible.
+ * For example, an AOSP-defined Parcelable <code>AospDefinedParcelable</code>
+ * is expected to be extended by device implementers for their value-add features.
+ * Previously without ParcelableHolder, the device implementers had to
+ * directly modify the Parcelable to add more fields:
+ * <pre> {@code
+ * parcelable AospDefinedParcelable {
+ * int a;
+ * String b;
+ * String x; // added by a device implementer
+ * int[] y; // added by a device implementer
+ * }}</pre>
+ *
+ * This practice is very error-prone because the fields added by the device implementer
+ * might have a conflict when the Parcelable is revisioned in the next releases of Android.
+ *
+ * Using ParcelableHolder, one can define an extension point in a Parcelable.
+ * <pre> {@code
+ * parcelable AospDefinedParcelable {
+ * int a;
+ * String b;
+ * ParcelableHolder extension;
+ * }}</pre>
+ * Then the device implementers can define their own Parcelable for their extension.
+ *
+ * <pre> {@code
+ * parcelable OemDefinedParcelable {
+ * String x;
+ * int[] y;
+ * }}</pre>
+ * Finally, the new Parcelable can be attached to the original Parcelable via
+ * the ParcelableHolder field.
+ *
+ * <pre> {@code
+ * AospDefinedParcelable ap = ...;
+ * OemDefinedParcelable op = new OemDefinedParcelable();
+ * op.x = ...;
+ * op.y = ...;
+ * ap.extension.setParcelable(op);}</pre>
+ *
+ * <p class="note">ParcelableHolder is <strong>not</strong> thread-safe.</p>
+ *
+ * @hide
+ */
+@SystemApi
+public final class ParcelableHolder implements Parcelable {
+ /**
+ * This is set by {@link #setParcelable}.
+ * {@link #mParcelable} and {@link #mParcel} are mutually exclusive
+ * if {@link ParcelableHolder} contains value, otherwise, both are null.
+ */
+ private Parcelable mParcelable;
+ /**
+ * This is set by {@link #readFromParcel}.
+ * {@link #mParcelable} and {@link #mParcel} are mutually exclusive
+ * if {@link ParcelableHolder} contains value, otherwise, both are null.
+ */
+ private Parcel mParcel;
+ private @Parcelable.Stability int mStability = Parcelable.PARCELABLE_STABILITY_LOCAL;
+
+ public ParcelableHolder(@Parcelable.Stability int stability) {
+ mStability = stability;
+ }
+
+ private ParcelableHolder() {
+
+ }
+
+ /**
+ * {@link ParcelableHolder}'s stability is determined by the parcelable
+ * which contains this ParcelableHolder.
+ * For more detail refer to {@link Parcelable#getStability}.
+ */
+ @Override
+ public @Parcelable.Stability int getStability() {
+ return mStability;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ParcelableHolder> CREATOR =
+ new Parcelable.Creator<ParcelableHolder>() {
+ @NonNull
+ @Override
+ public ParcelableHolder createFromParcel(@NonNull Parcel parcel) {
+ ParcelableHolder parcelable = new ParcelableHolder();
+ parcelable.readFromParcel(parcel);
+ return parcelable;
+ }
+
+ @NonNull
+ @Override
+ public ParcelableHolder[] newArray(int size) {
+ return new ParcelableHolder[size];
+ }
+ };
+
+
+ /**
+ * Write a parcelable into ParcelableHolder, the previous parcelable will be removed.
+ * (@link #setParcelable} and (@link #getParcelable} are not thread-safe.
+ * @throws BadParcelableException if the parcelable's stability is more unstable
+ * ParcelableHolder.
+ */
+ public void setParcelable(@Nullable Parcelable p) {
+ // A ParcelableHolder can only hold things at its stability or higher.
+ if (p != null && this.getStability() > p.getStability()) {
+ throw new BadParcelableException(
+ "A ParcelableHolder can only hold things at its stability or higher. "
+ + "The ParcelableHolder's stability is " + this.getStability()
+ + ", but the parcelable's stability is " + p.getStability());
+ }
+ mParcelable = p;
+ if (mParcel != null) {
+ mParcel.recycle();
+ mParcel = null;
+ }
+ }
+
+ /**
+ * Read a parcelable from ParcelableHolder.
+ * (@link #setParcelable} and (@link #getParcelable} are not thread-safe.
+ * @return the parcelable that was written by {@link #setParcelable} or {@link #readFromParcel},
+ * or {@code null} if the parcelable has not been written.
+ * @throws BadParcelableException if T is different from the type written by
+ * (@link #setParcelable}.
+ */
+ @Nullable
+ public <T extends Parcelable> T getParcelable(@NonNull Class<T> clazz) {
+ if (mParcel == null) {
+ if (mParcelable != null && !clazz.isInstance(mParcelable)) {
+ throw new BadParcelableException(
+ "The ParcelableHolder has " + mParcelable.getClass().getName()
+ + ", but the requested type is " + clazz.getName());
+ }
+ return (T) mParcelable;
+ }
+
+ mParcel.setDataPosition(0);
+
+ T parcelable = mParcel.readParcelable(clazz.getClassLoader());
+ if (parcelable != null && !clazz.isInstance(parcelable)) {
+ throw new BadParcelableException(
+ "The ParcelableHolder has " + parcelable.getClass().getName()
+ + ", but the requested type is " + clazz.getName());
+ }
+ mParcelable = parcelable;
+
+ mParcel.recycle();
+ mParcel = null;
+ return parcelable;
+ }
+
+ /**
+ * Read ParcelableHolder from a parcel.
+ */
+ public void readFromParcel(@NonNull Parcel parcel) {
+ int wireStability = parcel.readInt();
+ if (this.mStability != wireStability) {
+ throw new IllegalArgumentException("Expected stability " + this.mStability
+ + " but got " + wireStability);
+ }
+
+ mParcelable = null;
+
+ int dataSize = parcel.readInt();
+ if (dataSize < 0) {
+ throw new IllegalArgumentException("dataSize from parcel is negative");
+ } else if (dataSize == 0) {
+ if (mParcel != null) {
+ mParcel.recycle();
+ mParcel = null;
+ }
+ return;
+ }
+ if (mParcel == null) {
+ mParcel = Parcel.obtain();
+ }
+ mParcel.setDataPosition(0);
+ mParcel.setDataSize(0);
+ int dataStartPos = parcel.dataPosition();
+
+ mParcel.appendFrom(parcel, dataStartPos, dataSize);
+ parcel.setDataPosition(MathUtils.addOrThrow(dataStartPos, dataSize));
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(this.mStability);
+
+ if (mParcel != null) {
+ parcel.writeInt(mParcel.dataSize());
+ parcel.appendFrom(mParcel, 0, mParcel.dataSize());
+ return;
+ }
+
+ if (mParcelable == null) {
+ parcel.writeInt(0);
+ return;
+ }
+
+ int sizePos = parcel.dataPosition();
+ parcel.writeInt(0);
+ int dataStartPos = parcel.dataPosition();
+ parcel.writeParcelable(mParcelable, 0);
+ int dataSize = parcel.dataPosition() - dataStartPos;
+
+ parcel.setDataPosition(sizePos);
+ parcel.writeInt(dataSize);
+ parcel.setDataPosition(MathUtils.addOrThrow(parcel.dataPosition(), dataSize));
+ }
+
+ @Override
+ public int describeContents() {
+ if (mParcel != null) {
+ return mParcel.hasFileDescriptors() ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+ if (mParcelable != null) {
+ return mParcelable.describeContents();
+ }
+ return 0;
+ }
+}
diff --git a/android-34/android/os/ParcelableParcel.java b/android-34/android/os/ParcelableParcel.java
new file mode 100644
index 0000000..3be630f
--- /dev/null
+++ b/android-34/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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Parcel getParcel() {
+ mParcel.setDataPosition(0);
+ return mParcel;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/PatternMatcher.java b/android-34/android/os/PatternMatcher.java
new file mode 100644
index 0000000..b5425b4
--- /dev/null
+++ b/android-34/android/os/PatternMatcher.java
@@ -0,0 +1,600 @@
+/*
+ * 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.Log;
+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;
+
+ /**
+ * Pattern type: the given pattern must match the
+ * end of the string it is tested against.
+ */
+ public static final int PATTERN_SUFFIX = 4;
+
+ // 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;
+ case PATTERN_SUFFIX:
+ type = "SUFFIX: ";
+ break;
+ }
+ return "PatternMatcher{" + type + mPattern + "}";
+ }
+
+ /** @hide */
+ public void dumpDebug(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);
+ }
+
+ /**
+ * Perform a check on the matcher for the pattern type of {@link #PATTERN_ADVANCED_GLOB}.
+ * Return true if it passed.
+ * @hide
+ */
+ public boolean check() {
+ try {
+ if (mType == PATTERN_ADVANCED_GLOB) {
+ return Arrays.equals(mParsedPattern, parseAndVerifyAdvancedPattern(mPattern));
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Failed to verify advanced pattern: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ 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);
+ } else if (type == PATTERN_SUFFIX) {
+ return match.endsWith(pattern);
+ }
+ 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-34/android/os/PerformanceCollector.java b/android-34/android/os/PerformanceCollector.java
new file mode 100644
index 0000000..e6471ae
--- /dev/null
+++ b/android-34/android/os/PerformanceCollector.java
@@ -0,0 +1,594 @@
+/*
+ * 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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/PerformanceHintManager.java b/android-34/android/os/PerformanceHintManager.java
new file mode 100644
index 0000000..f79d6e6
--- /dev/null
+++ b/android-34/android/os/PerformanceHintManager.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.Closeable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
+
+
+/** The PerformanceHintManager allows apps to send performance hint to system. */
+@SystemService(Context.PERFORMANCE_HINT_SERVICE)
+public final class PerformanceHintManager {
+ private final long mNativeManagerPtr;
+
+ /** @hide */
+ public static PerformanceHintManager create() throws ServiceManager.ServiceNotFoundException {
+ long nativeManagerPtr = nativeAcquireManager();
+ if (nativeManagerPtr == 0) {
+ throw new ServiceManager.ServiceNotFoundException(Context.PERFORMANCE_HINT_SERVICE);
+ }
+ return new PerformanceHintManager(nativeManagerPtr);
+ }
+
+ private PerformanceHintManager(long nativeManagerPtr) {
+ mNativeManagerPtr = nativeManagerPtr;
+ }
+
+ /**
+ * Creates a {@link Session} for the given set of threads and sets their initial target work
+ * duration.
+ *
+ * @param tids The list of threads to be associated with this session. They must be part of
+ * this process' thread group.
+ * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new
+ * session.
+ * @return the new session if it is supported on this device, null if hint session is not
+ * supported on this device.
+ */
+ @Nullable
+ public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) {
+ Preconditions.checkNotNull(tids, "tids cannot be null");
+ Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos,
+ "the hint target duration should be positive.");
+ long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids,
+ initialTargetWorkDurationNanos);
+ if (nativeSessionPtr == 0) return null;
+ return new Session(nativeSessionPtr);
+ }
+
+ /**
+ * Get preferred update rate information for this device.
+ *
+ * @return the preferred update rate supported by device software.
+ */
+ public long getPreferredUpdateRateNanos() {
+ return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
+ }
+
+ /**
+ * A Session represents a group of threads with an inter-related workload such that hints for
+ * their performance should be considered as a unit. The threads in a given session should be
+ * long-life and not created or destroyed dynamically.
+ *
+ * <p>Each session is expected to have a periodic workload with a target duration for each
+ * cycle. The cycle duration is likely greater than the target work duration to allow other
+ * parts of the pipeline to run within the available budget. For example, a renderer thread may
+ * work at 60hz in order to produce frames at the display's frame but have a target work
+ * duration of only 6ms.</p>
+ *
+ * <p>Any call in this class will change its internal data, so you must do your own thread
+ * safety to protect from racing.</p>
+ *
+ * <p>Note that the target work duration can be {@link #updateTargetWorkDuration(long) updated}
+ * if workloads change.</p>
+ *
+ * <p>After each cycle of work, the client is expected to
+ * {@link #reportActualWorkDuration(long) report} the actual time taken to complete.</p>
+ *
+ * <p>All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.</p>
+ */
+ public static class Session implements Closeable {
+ private long mNativeSessionPtr;
+
+ /** @hide */
+ public Session(long nativeSessionPtr) {
+ mNativeSessionPtr = nativeSessionPtr;
+ }
+
+ /**
+ * This hint indicates a sudden increase in CPU workload intensity. It means
+ * that this hint session needs extra CPU resources immediately to meet the
+ * target duration for the current work cycle.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int CPU_LOAD_UP = 0;
+ /**
+ * This hint indicates a decrease in CPU workload intensity. It means that
+ * this hint session can reduce CPU resources and still meet the target duration.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int CPU_LOAD_DOWN = 1;
+ /**
+ * This hint indicates an upcoming CPU workload that is completely changed and
+ * unknown. It means that the hint session should reset CPU resources to a known
+ * baseline to prepare for an arbitrary load, and must wake up if inactive.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int CPU_LOAD_RESET = 2;
+ /**
+ * This hint indicates that the most recent CPU workload is resuming after a
+ * period of inactivity. It means that the hint session should allocate similar
+ * CPU resources to what was used previously, and must wake up if inactive.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int CPU_LOAD_RESUME = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CPU_LOAD_"}, value = {
+ CPU_LOAD_UP,
+ CPU_LOAD_DOWN,
+ CPU_LOAD_RESET,
+ CPU_LOAD_RESUME
+ })
+ public @interface Hint {}
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Updates this session's target duration for each cycle of work.
+ *
+ * @param targetDurationNanos the new desired duration in nanoseconds
+ */
+ public void updateTargetWorkDuration(long targetDurationNanos) {
+ Preconditions.checkArgumentPositive(targetDurationNanos, "the hint target duration"
+ + " should be positive.");
+ nativeUpdateTargetWorkDuration(mNativeSessionPtr, targetDurationNanos);
+ }
+
+ /**
+ * Reports the actual duration for the last cycle of work.
+ *
+ * <p>The system will attempt to adjust the core placement of the threads within the thread
+ * group and/or the frequency of the core on which they are run to bring the actual duration
+ * close to the target duration.</p>
+ *
+ * @param actualDurationNanos how long the thread group took to complete its last task in
+ * nanoseconds
+ */
+ public void reportActualWorkDuration(long actualDurationNanos) {
+ Preconditions.checkArgumentPositive(actualDurationNanos, "the actual duration should"
+ + " be positive.");
+ nativeReportActualWorkDuration(mNativeSessionPtr, actualDurationNanos);
+ }
+
+ /**
+ * Ends the current hint session.
+ *
+ * <p>Once called, you should not call anything else on this object.</p>
+ */
+ public void close() {
+ if (mNativeSessionPtr != 0) {
+ nativeCloseSession(mNativeSessionPtr);
+ mNativeSessionPtr = 0;
+ }
+ }
+
+ /**
+ * Sends performance hints to inform the hint session of changes in the workload.
+ *
+ * @param hint The hint to send to the session.
+ *
+ * @hide
+ */
+ @TestApi
+ public void sendHint(@Hint int hint) {
+ Preconditions.checkArgumentNonNegative(hint, "the hint ID should be at least"
+ + " zero.");
+ try {
+ nativeSendHint(mNativeSessionPtr, hint);
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
+
+ /**
+ * Set a list of threads to the performance hint session. This operation will replace
+ * the current list of threads with the given list of threads.
+ * Note that this is not an oneway method.
+ *
+ * @param tids The list of threads to be associated with this session. They must be
+ * part of this app's thread group.
+ *
+ * @throws IllegalStateException if the hint session is not in the foreground.
+ * @throws IllegalArgumentException if the thread id list is empty.
+ * @throws SecurityException if any thread id doesn't belong to the application.
+ */
+ public void setThreads(@NonNull int[] tids) {
+ if (mNativeSessionPtr == 0) {
+ return;
+ }
+ if (tids.length == 0) {
+ throw new IllegalArgumentException("Thread id list can't be empty.");
+ }
+ nativeSetThreads(mNativeSessionPtr, tids);
+ }
+
+ /**
+ * Returns the list of thread ids.
+ *
+ * @hide
+ */
+ @TestApi
+ public @Nullable int[] getThreadIds() {
+ return nativeGetThreadIds(mNativeSessionPtr);
+ }
+ }
+
+ private static native long nativeAcquireManager();
+ private static native long nativeGetPreferredUpdateRateNanos(long nativeManagerPtr);
+ private static native long nativeCreateSession(long nativeManagerPtr,
+ int[] tids, long initialTargetWorkDurationNanos);
+ private static native int[] nativeGetThreadIds(long nativeSessionPtr);
+ private static native void nativeUpdateTargetWorkDuration(long nativeSessionPtr,
+ long targetDurationNanos);
+ private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
+ long actualDurationNanos);
+ private static native void nativeCloseSession(long nativeSessionPtr);
+ private static native void nativeSendHint(long nativeSessionPtr, int hint);
+ private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
+}
diff --git a/android-34/android/os/PermissionEnforcer.java b/android-34/android/os/PermissionEnforcer.java
new file mode 100644
index 0000000..310ceb3
--- /dev/null
+++ b/android-34/android/os/PermissionEnforcer.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.app.AppOpsManager;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
+import android.permission.PermissionCheckerManager;
+
+/**
+ * PermissionEnforcer check permissions for AIDL-generated services which use
+ * the @EnforcePermission annotation.
+ *
+ * <p>AIDL services may be annotated with @EnforcePermission which will trigger
+ * the generation of permission check code. This generated code relies on
+ * PermissionEnforcer to validate the permissions. The methods available are
+ * purposely similar to the AIDL annotation syntax.
+ *
+ * @see android.permission.PermissionManager
+ *
+ * @hide
+ */
+@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+public class PermissionEnforcer {
+
+ private final Context mContext;
+ private static final String ACCESS_DENIED = "Access denied, requires: ";
+
+ /** Protected constructor. Allows subclasses to instantiate an object
+ * without using a Context.
+ */
+ protected PermissionEnforcer() {
+ mContext = null;
+ }
+
+ /** Constructor, prefer using the fromContext static method when possible */
+ public PermissionEnforcer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ protected int checkPermission(@NonNull String permission, @NonNull AttributionSource source) {
+ return PermissionChecker.checkPermissionForDataDelivery(
+ mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
+ }
+
+ @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
+ @PermissionCheckerManager.PermissionResult
+ protected int checkPermission(@NonNull String permission, int pid, int uid) {
+ if (mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return PermissionCheckerManager.PERMISSION_GRANTED;
+ }
+ return PermissionCheckerManager.PERMISSION_HARD_DENIED;
+ }
+
+ private boolean anyAppOps(@NonNull String[] permissions) {
+ for (String permission : permissions) {
+ if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void enforcePermission(@NonNull String permission, @NonNull
+ AttributionSource source) throws SecurityException {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException(ACCESS_DENIED + permission);
+ }
+ }
+
+ public void enforcePermission(@NonNull String permission, int pid, int uid)
+ throws SecurityException {
+ if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ AttributionSource source = new AttributionSource(uid, null, null);
+ enforcePermission(permission, source);
+ return;
+ }
+ int result = checkPermission(permission, pid, uid);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException(ACCESS_DENIED + permission);
+ }
+ }
+
+ public void enforcePermissionAllOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException(ACCESS_DENIED + "allOf={"
+ + String.join(", ", permissions) + "}");
+ }
+ }
+ }
+
+ public void enforcePermissionAllOf(@NonNull String[] permissions,
+ int pid, int uid) throws SecurityException {
+ if (anyAppOps(permissions)) {
+ AttributionSource source = new AttributionSource(uid, null, null);
+ enforcePermissionAllOf(permissions, source);
+ return;
+ }
+ for (String permission : permissions) {
+ int result = checkPermission(permission, pid, uid);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException(ACCESS_DENIED + "allOf={"
+ + String.join(", ", permissions) + "}");
+ }
+ }
+ }
+
+ public void enforcePermissionAnyOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException(ACCESS_DENIED + "anyOf={"
+ + String.join(", ", permissions) + "}");
+ }
+
+ public void enforcePermissionAnyOf(@NonNull String[] permissions,
+ int pid, int uid) throws SecurityException {
+ if (anyAppOps(permissions)) {
+ AttributionSource source = new AttributionSource(uid, null, null);
+ enforcePermissionAnyOf(permissions, source);
+ return;
+ }
+ for (String permission : permissions) {
+ int result = checkPermission(permission, pid, uid);
+ if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException(ACCESS_DENIED + "anyOf={"
+ + String.join(", ", permissions) + "}");
+ }
+
+ /**
+ * Returns a new PermissionEnforcer based on a Context.
+ *
+ * @hide
+ */
+ public static PermissionEnforcer fromContext(@NonNull Context context) {
+ return context.getSystemService(PermissionEnforcer.class);
+ }
+}
diff --git a/android-34/android/os/PersistableBundle.java b/android-34/android/os/PersistableBundle.java
new file mode 100644
index 0000000..02704f5
--- /dev/null
+++ b/android-34/android/os/PersistableBundle.java
@@ -0,0 +1,444 @@
+/*
+ * 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 java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+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.
+ *
+ * <p><b>Warning:</b> Note that {@link PersistableBundle} is a lazy container and as such it does
+ * NOT implement {@link #equals(Object)} or {@link #hashCode()}.
+ *
+ * @see Bundle
+ */
+public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
+ XmlUtils.WriteMapCallback {
+ private static final String TAG = "PersistableBundle";
+
+ private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
+
+ /** An unmodifiable {@code PersistableBundle} that is always {@link #isEmpty() empty}. */
+ 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.
+ *
+ * <p><b>Warning:</b> This method will deserialize every item on the bundle, including custom
+ * types such as {@link Parcelable} and {@link Serializable}, so only use this when you trust
+ * the source. Specifically don't use this method on app-provided bundles.
+ *
+ * @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, true);
+ }
+
+ private PersistableBundle(Bundle b, boolean throwException) {
+ this(b.getItemwiseMap(), throwException);
+ }
+
+ /**
+ * 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, boolean throwException) {
+ 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 = N - 1; i >= 0; --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, throwException));
+ } else if (value instanceof Bundle) {
+ mMap.setValueAt(i, new PersistableBundle((Bundle) value, throwException));
+ } else if (!isValidType(value)) {
+ final String errorMsg = "Bad value in PersistableBundle key="
+ + mMap.keyAt(i) + " value=" + value;
+ if (throwException) {
+ throw new IllegalArgumentException(errorMsg);
+ } else {
+ Slog.wtfStack(TAG, errorMsg);
+ mMap.removeAt(i);
+ }
+ }
+ }
+ }
+
+ /* package */ PersistableBundle(Parcel parcelledData, int length) {
+ super(parcelledData, length);
+ mFlags = FLAG_DEFUSABLE;
+ }
+
+ /**
+ * Constructs a {@link PersistableBundle} containing a copy of {@code from}.
+ *
+ * @param from The bundle to be copied.
+ * @param deep Whether is a deep or shallow copy.
+ *
+ * @hide
+ */
+ PersistableBundle(PersistableBundle from, boolean deep) {
+ super(from, deep);
+ }
+
+ /**
+ * 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() {
+ return new PersistableBundle(this, /* deep */ true);
+ }
+
+ /**
+ * 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, TypedXmlSerializer 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 {
+ saveToXml(XmlUtils.makeTyped(out));
+ }
+
+ /** @hide */
+ public void saveToXml(TypedXmlSerializer out) throws IOException, XmlPullParserException {
+ unparcel();
+ // Explicitly drop invalid types an attacker may have added before persisting.
+ for (int i = mMap.size() - 1; i >= 0; --i) {
+ final Object value = mMap.valueAt(i);
+ if (!isValidType(value)) {
+ Slog.e(TAG, "Dropping bad data before persisting: "
+ + mMap.keyAt(i) + "=" + value);
+ mMap.removeAt(i);
+ }
+ }
+ XmlUtils.writeMapXml(mMap, out, this);
+ }
+
+ /** @hide */
+ static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
+ @Override
+ public Object readThisUnknownObjectXml(TypedXmlPullParser 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 {
+ return restoreFromXml(XmlUtils.makeTyped(in));
+ }
+
+ /** @hide */
+ public static PersistableBundle restoreFromXml(TypedXmlPullParser 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) {
+ // Don't throw an exception when restoring from XML since an attacker could try to
+ // input invalid data in the persisted file.
+ return new PersistableBundle((ArrayMap<String, Object>)
+ XmlUtils.readThisArrayMapXml(in, startTag, tagName,
+ new MyReadMapCallback()),
+ /* throwException */ false);
+ }
+ }
+ return new PersistableBundle(); // An empty mutable PersistableBundle
+ }
+
+ /**
+ * Returns a string representation of the {@link PersistableBundle} that may be suitable for
+ * debugging. It won't print the internal map if its content hasn't been unparcelled.
+ */
+ @Override
+ public synchronized 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 dumpDebug(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);
+ }
+
+ /**
+ * Writes the content of the {@link PersistableBundle} to a {@link OutputStream}.
+ *
+ * <p>The content can be read by a {@link #readFromStream}.
+ *
+ * @see #readFromStream
+ */
+ public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(outputStream, UTF_8.name());
+ serializer.startTag(null, "bundle");
+ try {
+ saveToXml(serializer);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ serializer.endTag(null, "bundle");
+ serializer.flush();
+ }
+
+ /**
+ * Reads a {@link PersistableBundle} from an {@link InputStream}.
+ *
+ * <p>The stream must be generated by {@link #writeToStream}.
+ *
+ * @see #writeToStream
+ */
+ @NonNull
+ public static PersistableBundle readFromStream(@NonNull InputStream inputStream)
+ throws IOException {
+ try {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(inputStream, UTF_8.name());
+ parser.next();
+ return PersistableBundle.restoreFromXml(parser);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ }
+}
diff --git a/android-34/android/os/PooledStringReader.java b/android-34/android/os/PooledStringReader.java
new file mode 100644
index 0000000..6fc71c7
--- /dev/null
+++ b/android-34/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-34/android/os/PooledStringWriter.java b/android-34/android/os/PooledStringWriter.java
new file mode 100644
index 0000000..ee592d9
--- /dev/null
+++ b/android-34/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-34/android/os/PowerComponents.java b/android-34/android/os/PowerComponents.java
new file mode 100644
index 0000000..5dffa0a
--- /dev/null
+++ b/android-34/android/os/PowerComponents.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.BatteryConsumer.POWER_COMPONENT_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
+import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Contains details of battery attribution data broken down to individual power drain types
+ * such as CPU, RAM, GPU etc.
+ *
+ * @hide
+ */
+class PowerComponents {
+ private final BatteryConsumer.BatteryConsumerData mData;
+
+ PowerComponents(@NonNull Builder builder) {
+ mData = builder.mData;
+ }
+
+ PowerComponents(BatteryConsumer.BatteryConsumerData data) {
+ mData = data;
+ }
+
+ /**
+ * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
+ */
+ public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
+ if (dimensions.powerComponent != POWER_COMPONENT_ANY) {
+ return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
+ dimensions.processState).mPowerColumnIndex);
+ } else if (dimensions.processState != PROCESS_STATE_ANY) {
+ if (!mData.layout.processStateDataIncluded) {
+ throw new IllegalArgumentException(
+ "No data included in BatteryUsageStats for " + dimensions);
+ }
+ final BatteryConsumer.Key[] keys =
+ mData.layout.processStateKeys[dimensions.processState];
+ double totalPowerMah = 0;
+ for (int i = keys.length - 1; i >= 0; i--) {
+ totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
+ }
+ return totalPowerMah;
+ } else {
+ return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
+ }
+ }
+
+ /**
+ * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
+ *
+ * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
+ * or {@link BatteryConsumer#getKeys} method.
+ * @return Amount of consumed power in mAh.
+ */
+ public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
+ return mData.getDouble(key.mPowerColumnIndex);
+ }
+
+ /**
+ * Returns the amount of drain attributed to the specified custom drain type.
+ *
+ * @param componentId The ID of the custom power component.
+ * @return Amount of consumed power in mAh.
+ */
+ public double getConsumedPowerForCustomComponent(int componentId) {
+ final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ if (index >= 0 && index < mData.layout.customPowerComponentCount) {
+ return mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported custom power component ID: " + componentId);
+ }
+ }
+
+ public String getCustomPowerComponentName(int componentId) {
+ final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ if (index >= 0 && index < mData.layout.customPowerComponentCount) {
+ try {
+ return mData.layout.customPowerComponentNames[index];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException(
+ "Unsupported custom power component ID: " + componentId);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported custom power component ID: " + componentId);
+ }
+ }
+
+ @BatteryConsumer.PowerModel
+ int getPowerModel(BatteryConsumer.Key key) {
+ if (key.mPowerModelColumnIndex == -1) {
+ throw new IllegalStateException(
+ "Power model IDs were not requested in the BatteryUsageStatsQuery");
+ }
+ return mData.getInt(key.mPowerModelColumnIndex);
+ }
+
+ /**
+ * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
+ *
+ * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
+ * or {@link BatteryConsumer#getKeys} method.
+ * @return Amount of time in milliseconds.
+ */
+ public long getUsageDurationMillis(BatteryConsumer.Key key) {
+ return mData.getLong(key.mDurationColumnIndex);
+ }
+
+ /**
+ * Returns the amount of usage time attributed to the specified custom component.
+ *
+ * @param componentId The ID of the custom power component.
+ * @return Amount of time in milliseconds.
+ */
+ public long getUsageDurationForCustomComponentMillis(int componentId) {
+ final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ if (index >= 0 && index < mData.layout.customPowerComponentCount) {
+ return mData.getLong(mData.layout.firstCustomUsageDurationColumn + index);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported custom power component ID: " + componentId);
+ }
+ }
+
+ public void dump(PrintWriter pw, boolean skipEmptyComponents) {
+ String separator = "";
+ StringBuilder sb = new StringBuilder();
+
+ for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+ for (BatteryConsumer.Key key: mData.getKeys(componentId)) {
+ final double componentPower = getConsumedPower(key);
+ final long durationMs = getUsageDurationMillis(key);
+ if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
+ continue;
+ }
+
+ sb.append(separator);
+ separator = " ";
+ sb.append(key.toShortString());
+ sb.append("=");
+ sb.append(BatteryStats.formatCharge(componentPower));
+
+ if (durationMs != 0) {
+ sb.append(" (");
+ BatteryStats.formatTimeMsNoSpace(sb, durationMs);
+ sb.append(")");
+ }
+ }
+ }
+
+ final int customComponentCount = mData.layout.customPowerComponentCount;
+ for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customComponentCount;
+ customComponentId++) {
+ final double customComponentPower =
+ getConsumedPowerForCustomComponent(customComponentId);
+ if (skipEmptyComponents && customComponentPower == 0) {
+ continue;
+ }
+ sb.append(separator);
+ separator = " ";
+ sb.append(getCustomPowerComponentName(customComponentId));
+ sb.append("=");
+ sb.append(BatteryStats.formatCharge(customComponentPower));
+ }
+
+ pw.print(sb);
+ }
+
+ /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
+ boolean hasStatsProtoData() {
+ return writeStatsProtoImpl(null);
+ }
+
+ /** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */
+ void writeStatsProto(@NonNull ProtoOutputStream proto) {
+ writeStatsProtoImpl(proto);
+ }
+
+ /**
+ * Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto,
+ * and writes it to the given proto if it is non-null.
+ */
+ private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
+ boolean interestingData = false;
+
+ for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
+ for (BatteryConsumer.Key key : keys) {
+ final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
+ final long durationMs = getUsageDurationMillis(key);
+
+ if (powerDeciCoulombs == 0 && durationMs == 0) {
+ // No interesting data. Make sure not to even write the COMPONENT int.
+ continue;
+ }
+
+ interestingData = true;
+ if (proto == null) {
+ // We're just asked whether there is data, not to actually write it.
+ // And there is.
+ return true;
+ }
+
+ if (key.processState == PROCESS_STATE_ANY) {
+ writePowerComponentUsage(proto,
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
+ componentId, powerDeciCoulombs, durationMs);
+ } else {
+ writePowerUsageSlice(proto, componentId, powerDeciCoulombs, durationMs,
+ key.processState);
+ }
+ }
+ }
+ for (int idx = 0; idx < mData.layout.customPowerComponentCount; idx++) {
+ final int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + idx;
+ final long powerDeciCoulombs =
+ convertMahToDeciCoulombs(getConsumedPowerForCustomComponent(componentId));
+ final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
+
+ if (powerDeciCoulombs == 0 && durationMs == 0) {
+ // No interesting data. Make sure not to even write the COMPONENT int.
+ continue;
+ }
+
+ interestingData = true;
+ if (proto == null) {
+ // We're just asked whether there is data, not to actually write it. And there is.
+ return true;
+ }
+
+ writePowerComponentUsage(proto,
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
+ componentId, powerDeciCoulombs, durationMs);
+ }
+ return interestingData;
+ }
+
+ private void writePowerUsageSlice(ProtoOutputStream proto, int componentId,
+ long powerDeciCoulombs, long durationMs, int processState) {
+ final long slicesToken =
+ proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.SLICES);
+ writePowerComponentUsage(proto,
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+ .POWER_COMPONENT,
+ componentId, powerDeciCoulombs, durationMs);
+
+ final int procState;
+ switch (processState) {
+ case BatteryConsumer.PROCESS_STATE_FOREGROUND:
+ procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+ .FOREGROUND;
+ break;
+ case BatteryConsumer.PROCESS_STATE_BACKGROUND:
+ procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+ .BACKGROUND;
+ break;
+ case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
+ procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+ .FOREGROUND_SERVICE;
+ break;
+ case BatteryConsumer.PROCESS_STATE_CACHED:
+ procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+ .CACHED;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown process state: " + processState);
+ }
+
+ proto.write(BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
+ .PROCESS_STATE, procState);
+
+ proto.end(slicesToken);
+ }
+
+ private void writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId,
+ long powerDeciCoulombs, long durationMs) {
+ final long token = proto.start(tag);
+ proto.write(
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
+ .COMPONENT,
+ componentId);
+ proto.write(
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
+ .POWER_DECI_COULOMBS,
+ powerDeciCoulombs);
+ proto.write(
+ BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
+ .DURATION_MILLIS,
+ durationMs);
+ proto.end(token);
+ }
+
+ void writeToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
+ for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+ final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
+ for (BatteryConsumer.Key key : keys) {
+ final double powerMah = getConsumedPower(key);
+ final long durationMs = getUsageDurationMillis(key);
+ if (powerMah == 0 && durationMs == 0) {
+ continue;
+ }
+
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
+ if (key.processState != PROCESS_STATE_UNSPECIFIED) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
+ key.processState);
+ }
+ if (powerMah != 0) {
+ serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
+ }
+ if (durationMs != 0) {
+ serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
+ }
+ if (mData.layout.powerModelsIncluded) {
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
+ getPowerModel(key));
+ }
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
+ }
+ }
+
+ final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + mData.layout.customPowerComponentCount;
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId < customComponentEnd;
+ componentId++) {
+ final double powerMah = getConsumedPowerForCustomComponent(componentId);
+ final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
+ if (powerMah == 0 && durationMs == 0) {
+ continue;
+ }
+
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
+ if (powerMah != 0) {
+ serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
+ }
+ if (durationMs != 0) {
+ serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
+ }
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
+ }
+
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
+ }
+
+
+ static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)
+ throws XmlPullParserException, IOException {
+ int eventType = parser.getEventType();
+ if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
+ BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
+ throw new XmlPullParserException("Invalid XML parser state");
+ }
+
+ while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
+ BatteryUsageStats.XML_TAG_POWER_COMPONENTS))
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case BatteryUsageStats.XML_TAG_COMPONENT: {
+ int componentId = -1;
+ int processState = PROCESS_STATE_UNSPECIFIED;
+ double powerMah = 0;
+ long durationMs = 0;
+ int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ switch (parser.getAttributeName(i)) {
+ case BatteryUsageStats.XML_ATTR_ID:
+ componentId = parser.getAttributeInt(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_PROCESS_STATE:
+ processState = parser.getAttributeInt(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_POWER:
+ powerMah = parser.getAttributeDouble(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_DURATION:
+ durationMs = parser.getAttributeLong(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_MODEL:
+ model = parser.getAttributeInt(i);
+ break;
+ }
+ }
+ final BatteryConsumer.Key key =
+ builder.mData.getKey(componentId, processState);
+ builder.setConsumedPower(key, powerMah, model);
+ builder.setUsageDurationMillis(key, durationMs);
+ break;
+ }
+ case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: {
+ int componentId = -1;
+ double powerMah = 0;
+ long durationMs = 0;
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ switch (parser.getAttributeName(i)) {
+ case BatteryUsageStats.XML_ATTR_ID:
+ componentId = parser.getAttributeInt(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_POWER:
+ powerMah = parser.getAttributeDouble(i);
+ break;
+ case BatteryUsageStats.XML_ATTR_DURATION:
+ durationMs = parser.getAttributeLong(i);
+ break;
+ }
+ }
+ builder.setConsumedPowerForCustomComponent(componentId, powerMah);
+ builder.setUsageDurationForCustomComponentMillis(componentId, durationMs);
+ break;
+ }
+ }
+ }
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * Builder for PowerComponents.
+ */
+ static final class Builder {
+ private static final byte POWER_MODEL_UNINITIALIZED = -1;
+
+ private final BatteryConsumer.BatteryConsumerData mData;
+
+ Builder(BatteryConsumer.BatteryConsumerData data) {
+ mData = data;
+ for (BatteryConsumer.Key[] keys : mData.layout.keys) {
+ for (BatteryConsumer.Key key : keys) {
+ if (key.mPowerModelColumnIndex != -1) {
+ mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
+ }
+ }
+ }
+ }
+
+ @NonNull
+ public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
+ int powerModel) {
+ mData.putDouble(key.mPowerColumnIndex, componentPower);
+ if (key.mPowerModelColumnIndex != -1) {
+ mData.putInt(key.mPowerModelColumnIndex, powerModel);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the amount of drain attributed to the specified custom drain type.
+ *
+ * @param componentId The ID of the custom power component.
+ * @param componentPower Amount of consumed power in mAh.
+ */
+ @NonNull
+ public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
+ final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ if (index < 0 || index >= mData.layout.customPowerComponentCount) {
+ throw new IllegalArgumentException(
+ "Unsupported custom power component ID: " + componentId);
+ }
+ mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index, componentPower);
+ return this;
+ }
+
+ @NonNull
+ public Builder setUsageDurationMillis(BatteryConsumer.Key key,
+ long componentUsageDurationMillis) {
+ mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis);
+ return this;
+ }
+
+ /**
+ * Sets the amount of time used by the specified custom component.
+ *
+ * @param componentId The ID of the custom power component.
+ * @param componentUsageDurationMillis Amount of time in milliseconds.
+ */
+ @NonNull
+ public Builder setUsageDurationForCustomComponentMillis(int componentId,
+ long componentUsageDurationMillis) {
+ final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ if (index < 0 || index >= mData.layout.customPowerComponentCount) {
+ throw new IllegalArgumentException(
+ "Unsupported custom power component ID: " + componentId);
+ }
+
+ mData.putLong(mData.layout.firstCustomUsageDurationColumn + index,
+ componentUsageDurationMillis);
+ return this;
+ }
+
+ public void addPowerAndDuration(PowerComponents.Builder other) {
+ addPowerAndDuration(other.mData);
+ }
+
+ public void addPowerAndDuration(PowerComponents other) {
+ addPowerAndDuration(other.mData);
+ }
+
+ private void addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData) {
+ if (mData.layout.customPowerComponentCount
+ != otherData.layout.customPowerComponentCount) {
+ throw new IllegalArgumentException(
+ "Number of custom power components does not match: "
+ + otherData.layout.customPowerComponentCount
+ + ", expected: " + mData.layout.customPowerComponentCount);
+ }
+
+ for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0;
+ componentId--) {
+ final BatteryConsumer.Key[] keys = mData.layout.keys[componentId];
+ for (BatteryConsumer.Key key: keys) {
+ BatteryConsumer.Key otherKey = null;
+ for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) {
+ if (aKey.equals(key)) {
+ otherKey = aKey;
+ break;
+ }
+ }
+
+ if (otherKey == null) {
+ continue;
+ }
+
+ mData.putDouble(key.mPowerColumnIndex,
+ mData.getDouble(key.mPowerColumnIndex)
+ + otherData.getDouble(otherKey.mPowerColumnIndex));
+ mData.putLong(key.mDurationColumnIndex,
+ mData.getLong(key.mDurationColumnIndex)
+ + otherData.getLong(otherKey.mDurationColumnIndex));
+
+ if (key.mPowerModelColumnIndex == -1) {
+ continue;
+ }
+
+ boolean undefined = false;
+ if (otherKey.mPowerModelColumnIndex == -1) {
+ undefined = true;
+ } else {
+ final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
+ int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
+ if (powerModel == POWER_MODEL_UNINITIALIZED) {
+ mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
+ } else if (powerModel != otherPowerModel
+ && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
+ undefined = true;
+ }
+ }
+
+ if (undefined) {
+ mData.putInt(key.mPowerModelColumnIndex,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
+ }
+ }
+
+ for (int i = mData.layout.customPowerComponentCount - 1; i >= 0; i--) {
+ final int powerColumnIndex = mData.layout.firstCustomConsumedPowerColumn + i;
+ final int otherPowerColumnIndex =
+ otherData.layout.firstCustomConsumedPowerColumn + i;
+ mData.putDouble(powerColumnIndex,
+ mData.getDouble(powerColumnIndex) + otherData.getDouble(
+ otherPowerColumnIndex));
+
+ final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
+ final int otherDurationColumnIndex =
+ otherData.layout.firstCustomUsageDurationColumn + i;
+ mData.putLong(usageColumnIndex,
+ mData.getLong(usageColumnIndex) + otherData.getLong(
+ otherDurationColumnIndex)
+ );
+ }
+ }
+
+ /**
+ * Returns the total power accumulated by this builder so far. It may change
+ * by the time the {@code build()} method is called.
+ */
+ public double getTotalPower() {
+ double totalPowerMah = 0;
+ for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+ totalPowerMah += mData.getDouble(
+ mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex);
+ }
+ for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
+ totalPowerMah += mData.getDouble(
+ mData.layout.firstCustomConsumedPowerColumn + i);
+ }
+ return totalPowerMah;
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public PowerComponents build() {
+ mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
+
+ for (BatteryConsumer.Key[] keys : mData.layout.keys) {
+ for (BatteryConsumer.Key key : keys) {
+ if (key.mPowerModelColumnIndex != -1) {
+ if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
+ mData.putInt(key.mPowerModelColumnIndex,
+ BatteryConsumer.POWER_MODEL_UNDEFINED);
+ }
+ }
+ }
+ }
+
+ return new PowerComponents(this);
+ }
+ }
+}
diff --git a/android-34/android/os/PowerExemptionManager.java b/android-34/android/os/PowerExemptionManager.java
new file mode 100644
index 0000000..17076bc
--- /dev/null
+++ b/android-34/android/os/PowerExemptionManager.java
@@ -0,0 +1,880 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+
+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.UserHandleAware;
+import android.content.Context;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Interface to access and modify the permanent and temporary power save allow list. The two lists
+ * are kept separately. Apps placed on the permanent allow list are only removed via an explicit
+ * {@link #removeFromPermanentAllowList(String)} call. Apps allow-listed by default by the system
+ * cannot be removed. Apps placed on the temporary allow list are removed from that allow list after
+ * a predetermined amount of time.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.POWER_EXEMPTION_SERVICE)
+public class PowerExemptionManager {
+ private final Context mContext;
+ // Proxy to DeviceIdleController for now
+ // TODO: migrate to PowerExemptionController
+ private final IDeviceIdleController mService;
+
+ /**
+ * Indicates that an unforeseen event has occurred and the app should be allow-listed to handle
+ * it.
+ */
+ public static final int EVENT_UNSPECIFIED = 0;
+
+ /**
+ * Indicates that an SMS event has occurred and the app should be allow-listed to handle it.
+ */
+ public static final int EVENT_SMS = 1;
+
+ /**
+ * Indicates that an MMS event has occurred and the app should be allow-listed to handle it.
+ */
+ public static final int EVENT_MMS = 2;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EVENT_"}, value = {
+ EVENT_UNSPECIFIED,
+ EVENT_SMS,
+ EVENT_MMS,
+ })
+ public @interface AllowListEvent {
+ }
+
+ /**
+ * Does not place the app on any temporary allow list. Nullifies the previous call to
+ * {@link android.app.BroadcastOptions#setTemporaryAppAllowlist(long, int, int, String)}.
+ * Note: this will not remove the receiver app from the temp allow list.
+ */
+ public static final int TEMPORARY_ALLOW_LIST_TYPE_NONE = -1;
+ /**
+ * Allow the temp allow list behavior, plus allow foreground service start from background.
+ */
+ public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0;
+ /**
+ * Only allow the temp allow list behavior, not allow foreground service start from background.
+ */
+ public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
+
+ /**
+ * Delay freezing the app when the broadcast is delivered. This flag is not required if
+ * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED or
+ * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED are specified, as those will
+ * already defer freezing during the allowlist duration.
+ * @hide temporarily until the next release
+ */
+ public static final int TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED = 1 << 2;
+
+ /**
+ * The list of temp allow list types.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "TEMPORARY_ALLOW_LIST_TYPE_" }, value = {
+ TEMPORARY_ALLOW_LIST_TYPE_NONE,
+ TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+ TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TempAllowListType {}
+
+ /* Reason codes for BG-FGS-launch. */
+ /**
+ * BG-FGS-launch is denied.
+ * @hide
+ */
+ public static final int REASON_DENIED = -1;
+
+ /* Reason code range 0-9 are reserved for default reasons */
+ /**
+ * The default reason code if reason is unknown.
+ */
+ public static final int REASON_UNKNOWN = 0;
+ /**
+ * Use REASON_OTHER if there is no better choice.
+ */
+ public static final int REASON_OTHER = 1;
+
+ /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */
+ /** @hide */
+ public static final int REASON_PROC_STATE_PERSISTENT = 10;
+ /** @hide */
+ public static final int REASON_PROC_STATE_PERSISTENT_UI = 11;
+ /** @hide */
+ public static final int REASON_PROC_STATE_TOP = 12;
+ /** @hide */
+ public static final int REASON_PROC_STATE_BTOP = 13;
+ /** @hide */
+ public static final int REASON_PROC_STATE_FGS = 14;
+ /** @hide */
+ public static final int REASON_PROC_STATE_BFGS = 15;
+
+ /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */
+ /** @hide */
+ public static final int REASON_UID_VISIBLE = 50;
+ /** @hide */
+ public static final int REASON_SYSTEM_UID = 51;
+ /** @hide */
+ public static final int REASON_ACTIVITY_STARTER = 52;
+ /** @hide */
+ public static final int REASON_START_ACTIVITY_FLAG = 53;
+ /** @hide */
+ public static final int REASON_FGS_BINDING = 54;
+ /** @hide */
+ public static final int REASON_DEVICE_OWNER = 55;
+ /** @hide */
+ public static final int REASON_PROFILE_OWNER = 56;
+ /** @hide */
+ public static final int REASON_COMPANION_DEVICE_MANAGER = 57;
+ /**
+ * START_ACTIVITIES_FROM_BACKGROUND permission.
+ * @hide
+ */
+ public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = 58;
+ /**
+ * START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
+ * @hide
+ */
+ public static final int REASON_BACKGROUND_FGS_PERMISSION = 59;
+ /** @hide */
+ public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60;
+ /** @hide */
+ public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61;
+ /** @hide */
+ public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62;
+ /** @hide */
+ public static final int REASON_DEVICE_DEMO_MODE = 63;
+ /** @hide */
+ public static final int REASON_ALLOWLISTED_PACKAGE = 65;
+ /** @hide */
+ public static final int REASON_APPOP = 66;
+ /** @hide */
+ public static final int REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD = 67;
+ /** @hide */
+ public static final int REASON_OP_ACTIVATE_VPN = 68;
+ /** @hide */
+ public static final int REASON_OP_ACTIVATE_PLATFORM_VPN = 69;
+ /**
+ * Temporarily allowed to have FGS while-in-use permissions.
+ * @hide
+ */
+ public static final int REASON_TEMP_ALLOWED_WHILE_IN_USE = 70;
+ /** @hide */
+ public static final int REASON_CURRENT_INPUT_METHOD = 71;
+
+ /* BG-FGS-launch is allowed by temp-allow-list or system-allow-list.
+ Reason code for temp and system allow list starts here.
+ Reason code range 100-199 are reserved for public reasons. */
+ /**
+ * Set temp-allow-list for location geofence purpose.
+ */
+ public static final int REASON_GEOFENCING = 100;
+ /**
+ * Set temp-allow-list for server push messaging.
+ */
+ public static final int REASON_PUSH_MESSAGING = 101;
+ /**
+ * Set temp-allow-list for server push messaging over the quota.
+ */
+ public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102;
+ /**
+ * Set temp-allow-list for activity recognition.
+ */
+ public static final int REASON_ACTIVITY_RECOGNITION = 103;
+ /**
+ * Set temp-allow-list for transferring accounts between users.
+ */
+ public static final int REASON_ACCOUNT_TRANSFER = 104;
+ /**
+ * Set temp-allow-list for server push messaging that can be deferred.
+ * @hide temporarily until the next release
+ */
+ public static final int REASON_PUSH_MESSAGING_DEFERRABLE = 105;
+
+ /* Reason code range 200-299 are reserved for broadcast actions */
+ /**
+ * Broadcast ACTION_BOOT_COMPLETED.
+ * @hide
+ */
+ public static final int REASON_BOOT_COMPLETED = 200;
+ /**
+ * Broadcast ACTION_PRE_BOOT_COMPLETED.
+ * @hide
+ */
+ public static final int REASON_PRE_BOOT_COMPLETED = 201;
+ /**
+ * Broadcast ACTION_LOCKED_BOOT_COMPLETED.
+ * @hide
+ */
+ public static final int REASON_LOCKED_BOOT_COMPLETED = 202;
+ /**
+ * All Bluetooth broadcasts.
+ */
+ public static final int REASON_BLUETOOTH_BROADCAST = 203;
+ /**
+ * Broadcast {@link android.content.Intent#ACTION_TIMEZONE_CHANGED}
+ * @hide
+ */
+ public static final int REASON_TIMEZONE_CHANGED = 204;
+ /**
+ * Broadcast {@link android.content.Intent#ACTION_TIME_CHANGED}
+ * @hide
+ */
+ public static final int REASON_TIME_CHANGED = 205;
+ /**
+ * Broadcast {@link android.content.Intent#ACTION_LOCALE_CHANGED}
+ * @hide
+ */
+ public static final int REASON_LOCALE_CHANGED = 206;
+ /**
+ * Broadcast
+ * {@link android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED}
+ * @hide
+ */
+ public static final int REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
+ /**
+ * Broadcast {@link android.safetycenter.SafetyCenterManager#ACTION_REFRESH_SAFETY_SOURCES}.
+ */
+ public static final int REASON_REFRESH_SAFETY_SOURCES = 208;
+
+ /* Reason code range 300-399 are reserved for other internal reasons */
+ /**
+ * Device idle system allow list, including EXCEPT-IDLE
+ * @hide
+ */
+ public static final int REASON_SYSTEM_ALLOW_LISTED = 300;
+ /** @hide */
+ public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
+ /**
+ * AlarmManagerService.
+ * @hide
+ */
+ public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302;
+ /**
+ * ActiveServices.
+ * @hide
+ */
+ public static final int REASON_SERVICE_LAUNCH = 303;
+ /**
+ * KeyChainSystemService.
+ * @hide
+ */
+ public static final int REASON_KEY_CHAIN = 304;
+ /**
+ * PackageManagerService.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_VERIFIER = 305;
+ /**
+ * SyncManager.
+ * @hide
+ */
+ public static final int REASON_SYNC_MANAGER = 306;
+ /**
+ * DomainVerificationProxyV1.
+ * @hide
+ */
+ public static final int REASON_DOMAIN_VERIFICATION_V1 = 307;
+ /**
+ * DomainVerificationProxyV2.
+ * @hide
+ */
+ public static final int REASON_DOMAIN_VERIFICATION_V2 = 308;
+ /** @hide */
+ public static final int REASON_VPN = 309;
+ /**
+ * NotificationManagerService.
+ * @hide
+ */
+ public static final int REASON_NOTIFICATION_SERVICE = 310;
+ /**
+ * Broadcast ACTION_MY_PACKAGE_REPLACED.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_REPLACED = 311;
+ /**
+ * LocationProvider.
+ * @hide
+ */
+ @SystemApi
+ public static final int REASON_LOCATION_PROVIDER = 312;
+ /**
+ * MediaButtonReceiver.
+ * @hide
+ */
+ public static final int REASON_MEDIA_BUTTON = 313;
+ /**
+ * InboundSmsHandler.
+ * @hide
+ */
+ public static final int REASON_EVENT_SMS = 314;
+ /**
+ * InboundSmsHandler.
+ * @hide
+ */
+ public static final int REASON_EVENT_MMS = 315;
+ /**
+ * Shell app.
+ * @hide
+ */
+ public static final int REASON_SHELL = 316;
+ /**
+ * Media session callbacks.
+ * @hide
+ */
+ public static final int REASON_MEDIA_SESSION_CALLBACK = 317;
+ /**
+ * Dialer app.
+ * @hide
+ */
+ public static final int REASON_ROLE_DIALER = 318;
+ /**
+ * Emergency app.
+ * @hide
+ */
+ public static final int REASON_ROLE_EMERGENCY = 319;
+ /**
+ * System Module.
+ * @hide
+ */
+ public static final int REASON_SYSTEM_MODULE = 320;
+ /**
+ * Carrier privileged app.
+ * @hide
+ */
+ public static final int REASON_CARRIER_PRIVILEGED_APP = 321;
+ /**
+ * Device/Profile owner protected apps.
+ * @hide
+ */
+ public static final int REASON_DPO_PROTECTED_APP = 322;
+ /**
+ * Apps control is disallowed for the user.
+ * @hide
+ */
+ public static final int REASON_DISALLOW_APPS_CONTROL = 323;
+ /**
+ * Active device admin package.
+ * @hide
+ */
+ public static final int REASON_ACTIVE_DEVICE_ADMIN = 324;
+
+ /**
+ * Media notification re-generate during transferring.
+ * @hide
+ */
+ public static final int REASON_MEDIA_NOTIFICATION_TRANSFER = 325;
+
+ /**
+ * Package installer.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_INSTALLER = 326;
+
+ /**
+ * {@link android.app.AppOpsManager#OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS}
+ * set to MODE_ALLOWED
+ * @hide
+ */
+ public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327;
+
+ /** @hide The app requests out-out. */
+ public static final int REASON_OPT_OUT_REQUESTED = 1000;
+
+ /**
+ * The list of BG-FGS-Launch and temp-allow-list reason code.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "REASON_" }, value = {
+ // BG-FGS-Launch reasons.
+ REASON_DENIED,
+ REASON_UNKNOWN,
+ REASON_OTHER,
+ REASON_PROC_STATE_PERSISTENT,
+ REASON_PROC_STATE_PERSISTENT_UI,
+ REASON_PROC_STATE_TOP,
+ REASON_PROC_STATE_BTOP,
+ REASON_PROC_STATE_FGS,
+ REASON_PROC_STATE_BFGS,
+ REASON_UID_VISIBLE,
+ REASON_SYSTEM_UID,
+ REASON_ACTIVITY_STARTER,
+ REASON_START_ACTIVITY_FLAG,
+ REASON_FGS_BINDING,
+ REASON_DEVICE_OWNER,
+ REASON_PROFILE_OWNER,
+ REASON_COMPANION_DEVICE_MANAGER,
+ REASON_BACKGROUND_ACTIVITY_PERMISSION,
+ REASON_BACKGROUND_FGS_PERMISSION,
+ REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION,
+ REASON_INSTR_BACKGROUND_FGS_PERMISSION,
+ REASON_SYSTEM_ALERT_WINDOW_PERMISSION,
+ REASON_DEVICE_DEMO_MODE,
+ REASON_ALLOWLISTED_PACKAGE,
+ REASON_APPOP,
+ REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD,
+ REASON_OP_ACTIVATE_VPN,
+ REASON_OP_ACTIVATE_PLATFORM_VPN,
+ REASON_CURRENT_INPUT_METHOD,
+ REASON_TEMP_ALLOWED_WHILE_IN_USE,
+ // temp and system allow list reasons.
+ REASON_GEOFENCING,
+ REASON_PUSH_MESSAGING,
+ REASON_PUSH_MESSAGING_OVER_QUOTA,
+ REASON_ACTIVITY_RECOGNITION,
+ REASON_ACCOUNT_TRANSFER,
+ REASON_PUSH_MESSAGING_DEFERRABLE,
+ REASON_BOOT_COMPLETED,
+ REASON_PRE_BOOT_COMPLETED,
+ REASON_LOCKED_BOOT_COMPLETED,
+ REASON_BLUETOOTH_BROADCAST,
+ REASON_TIMEZONE_CHANGED,
+ REASON_TIME_CHANGED,
+ REASON_LOCALE_CHANGED,
+ REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
+ REASON_REFRESH_SAFETY_SOURCES,
+ REASON_SYSTEM_ALLOW_LISTED,
+ REASON_ALARM_MANAGER_ALARM_CLOCK,
+ REASON_ALARM_MANAGER_WHILE_IDLE,
+ REASON_SERVICE_LAUNCH,
+ REASON_KEY_CHAIN,
+ REASON_PACKAGE_VERIFIER,
+ REASON_SYNC_MANAGER,
+ REASON_DOMAIN_VERIFICATION_V1,
+ REASON_DOMAIN_VERIFICATION_V2,
+ REASON_VPN,
+ REASON_NOTIFICATION_SERVICE,
+ REASON_PACKAGE_REPLACED,
+ REASON_LOCATION_PROVIDER,
+ REASON_MEDIA_BUTTON,
+ REASON_EVENT_SMS,
+ REASON_EVENT_MMS,
+ REASON_SHELL,
+ REASON_MEDIA_SESSION_CALLBACK,
+ REASON_ROLE_DIALER,
+ REASON_ROLE_EMERGENCY,
+ REASON_SYSTEM_MODULE,
+ REASON_CARRIER_PRIVILEGED_APP,
+ REASON_OPT_OUT_REQUESTED,
+ REASON_DPO_PROTECTED_APP,
+ REASON_DISALLOW_APPS_CONTROL,
+ REASON_ACTIVE_DEVICE_ADMIN,
+ REASON_MEDIA_NOTIFICATION_TRANSFER,
+ REASON_PACKAGE_INSTALLER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReasonCode {}
+
+ /**
+ * @hide
+ */
+ public PowerExemptionManager(@NonNull Context context) {
+ mContext = context;
+ mService = context.getSystemService(DeviceIdleManager.class).getService();
+ }
+
+ /**
+ * Add the specified package to the permanent power save allow list.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void addToPermanentAllowList(@NonNull String packageName) {
+ addToPermanentAllowList(Collections.singletonList(packageName));
+ }
+
+ /**
+ * Add the specified packages to the permanent power save allow list.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void addToPermanentAllowList(@NonNull List<String> packageNames) {
+ try {
+ mService.addPowerSaveWhitelistApps(packageNames);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get a list of app IDs of app that are allow-listed. This does not include temporarily
+ * allow-listed apps.
+ *
+ * @param includingIdle Set to true if the app should be allow-listed from device idle as well
+ * as other power save restrictions
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public int[] getAllowListedAppIds(boolean includingIdle) {
+ try {
+ if (includingIdle) {
+ return mService.getAppIdWhitelist();
+ } else {
+ return mService.getAppIdWhitelistExceptIdle();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if the app is allow-listed from power save restrictions. This does not include
+ * temporarily allow-listed apps.
+ *
+ * @param includingIdle Set to true if the app should be allow-listed from device
+ * idle as well as other power save restrictions
+ * @hide
+ */
+ public boolean isAllowListed(@NonNull String packageName, boolean includingIdle) {
+ try {
+ if (includingIdle) {
+ return mService.isPowerSaveWhitelistApp(packageName);
+ } else {
+ return mService.isPowerSaveWhitelistExceptIdleApp(packageName);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove an app from the permanent power save allow list. Only apps that were added via
+ * {@link #addToPermanentAllowList(String)} or {@link #addToPermanentAllowList(List)} will be
+ * removed. Apps allow-listed by default by the system cannot be removed.
+ *
+ * @param packageName The app to remove from the allow list
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void removeFromPermanentAllowList(@NonNull String packageName) {
+ try {
+ mService.removePowerSaveWhitelistApp(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Add an app to the temporary allow list for a short amount of time.
+ *
+ * @param packageName The package to add to the temp allow list
+ * @param durationMs How long to keep the app on the temp allow list for (in milliseconds)
+ * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
+ * @param reason a optional human readable reason string, could be null or empty string.
+ */
+ @UserHandleAware
+ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
+ public void addToTemporaryAllowList(@NonNull String packageName, @ReasonCode int reasonCode,
+ @Nullable String reason, long durationMs) {
+ try {
+ mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(),
+ reasonCode, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Add an app to the temporary allow list for a short amount of time for a specific reason.
+ * The temporary allow list is kept separately from the permanent allow list and apps are
+ * automatically removed from the temporary allow list after a predetermined amount of time.
+ *
+ * @param packageName The package to add to the temp allow list
+ * @param event The reason to add the app to the temp allow list
+ * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
+ * @param reason A human-readable reason explaining why the app is temp allow-listed. Only
+ * used for logging purposes. Could be null or empty string.
+ * @return The duration (in milliseconds) that the app is allow-listed for
+ */
+ @UserHandleAware
+ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
+ public long addToTemporaryAllowListForEvent(@NonNull String packageName,
+ @ReasonCode int reasonCode, @Nullable String reason, @AllowListEvent int event) {
+ try {
+ switch (event) {
+ case EVENT_MMS:
+ return mService.addPowerSaveTempWhitelistAppForMms(
+ packageName, mContext.getUserId(), reasonCode, reason);
+ case EVENT_SMS:
+ return mService.addPowerSaveTempWhitelistAppForSms(
+ packageName, mContext.getUserId(), reasonCode, reason);
+ case EVENT_UNSPECIFIED:
+ default:
+ return mService.whitelistAppTemporarily(
+ packageName, mContext.getUserId(), reasonCode, reason);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static @ReasonCode int getReasonCodeFromProcState(int procState) {
+ if (procState <= PROCESS_STATE_PERSISTENT) {
+ return REASON_PROC_STATE_PERSISTENT;
+ } else if (procState <= PROCESS_STATE_PERSISTENT_UI) {
+ return REASON_PROC_STATE_PERSISTENT_UI;
+ } else if (procState <= PROCESS_STATE_TOP) {
+ return REASON_PROC_STATE_TOP;
+ } else if (procState <= PROCESS_STATE_BOUND_TOP) {
+ return REASON_PROC_STATE_BTOP;
+ } else if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) {
+ return REASON_PROC_STATE_FGS;
+ } else if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ return REASON_PROC_STATE_BFGS;
+ } else {
+ return REASON_DENIED;
+ }
+ }
+
+ /**
+ * @hide
+ * @return the reason code mapped to statsd for the AppBackgroundRestrictionsInfo atom.
+ */
+ public static int getExemptionReasonForStatsd(@ReasonCode int reasonCode) {
+ switch (reasonCode) {
+ case REASON_SYSTEM_UID:
+ return AppBackgroundRestrictionsInfo.REASON_SYSTEM_UID;
+ case REASON_ALLOWLISTED_PACKAGE:
+ return AppBackgroundRestrictionsInfo.REASON_ALLOWLISTED_PACKAGE;
+ case REASON_COMPANION_DEVICE_MANAGER:
+ return AppBackgroundRestrictionsInfo.REASON_COMPANION_DEVICE_MANAGER;
+ case REASON_DEVICE_DEMO_MODE:
+ return AppBackgroundRestrictionsInfo.REASON_DEVICE_DEMO_MODE;
+ case REASON_DEVICE_OWNER:
+ return AppBackgroundRestrictionsInfo.REASON_DEVICE_OWNER;
+ case REASON_PROFILE_OWNER:
+ return AppBackgroundRestrictionsInfo.REASON_PROFILE_OWNER;
+ case REASON_PROC_STATE_PERSISTENT:
+ return AppBackgroundRestrictionsInfo.REASON_PROC_STATE_PERSISTENT;
+ case REASON_PROC_STATE_PERSISTENT_UI:
+ return AppBackgroundRestrictionsInfo.REASON_PROC_STATE_PERSISTENT_UI;
+ case REASON_OP_ACTIVATE_VPN:
+ return AppBackgroundRestrictionsInfo.REASON_OP_ACTIVATE_VPN;
+ case REASON_OP_ACTIVATE_PLATFORM_VPN:
+ return AppBackgroundRestrictionsInfo.REASON_OP_ACTIVATE_PLATFORM_VPN;
+ case REASON_SYSTEM_MODULE:
+ return AppBackgroundRestrictionsInfo.REASON_SYSTEM_MODULE;
+ case REASON_CARRIER_PRIVILEGED_APP:
+ return AppBackgroundRestrictionsInfo.REASON_CARRIER_PRIVILEGED_APP;
+ case REASON_SYSTEM_ALLOW_LISTED:
+ return AppBackgroundRestrictionsInfo.REASON_SYSTEM_ALLOW_LISTED;
+ case REASON_ROLE_DIALER:
+ return AppBackgroundRestrictionsInfo.REASON_ROLE_DIALER;
+ case REASON_ROLE_EMERGENCY:
+ return AppBackgroundRestrictionsInfo.REASON_ROLE_EMERGENCY;
+ case REASON_DPO_PROTECTED_APP:
+ return AppBackgroundRestrictionsInfo.REASON_DPO_PROTECTED_APP;
+ case REASON_DISALLOW_APPS_CONTROL:
+ return AppBackgroundRestrictionsInfo.REASON_DISALLOW_APPS_CONTROL;
+ case REASON_ACTIVE_DEVICE_ADMIN:
+ return AppBackgroundRestrictionsInfo.REASON_ACTIVE_DEVICE_ADMIN;
+ default:
+ return AppBackgroundRestrictionsInfo.REASON_DENIED;
+ }
+ }
+
+ /**
+ * Return string name of the integer reason code.
+ * @hide
+ * @param reasonCode
+ * @return string name of the reason code.
+ */
+ public static String reasonCodeToString(@ReasonCode int reasonCode) {
+ switch (reasonCode) {
+ case REASON_DENIED:
+ return "DENIED";
+ case REASON_UNKNOWN:
+ return "UNKNOWN";
+ case REASON_OTHER:
+ return "OTHER";
+ case REASON_PROC_STATE_PERSISTENT:
+ return "PROC_STATE_PERSISTENT";
+ case REASON_PROC_STATE_PERSISTENT_UI:
+ return "PROC_STATE_PERSISTENT_UI";
+ case REASON_PROC_STATE_TOP:
+ return "PROC_STATE_TOP";
+ case REASON_PROC_STATE_BTOP:
+ return "PROC_STATE_BTOP";
+ case REASON_PROC_STATE_FGS:
+ return "PROC_STATE_FGS";
+ case REASON_PROC_STATE_BFGS:
+ return "PROC_STATE_BFGS";
+ case REASON_UID_VISIBLE:
+ return "UID_VISIBLE";
+ case REASON_SYSTEM_UID:
+ return "SYSTEM_UID";
+ case REASON_ACTIVITY_STARTER:
+ return "ACTIVITY_STARTER";
+ case REASON_START_ACTIVITY_FLAG:
+ return "START_ACTIVITY_FLAG";
+ case REASON_FGS_BINDING:
+ return "FGS_BINDING";
+ case REASON_DEVICE_OWNER:
+ return "DEVICE_OWNER";
+ case REASON_PROFILE_OWNER:
+ return "PROFILE_OWNER";
+ case REASON_COMPANION_DEVICE_MANAGER:
+ return "COMPANION_DEVICE_MANAGER";
+ case REASON_BACKGROUND_ACTIVITY_PERMISSION:
+ return "BACKGROUND_ACTIVITY_PERMISSION";
+ case REASON_BACKGROUND_FGS_PERMISSION:
+ return "BACKGROUND_FGS_PERMISSION";
+ case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION:
+ return "INSTR_BACKGROUND_ACTIVITY_PERMISSION";
+ case REASON_INSTR_BACKGROUND_FGS_PERMISSION:
+ return "INSTR_BACKGROUND_FGS_PERMISSION";
+ case REASON_SYSTEM_ALERT_WINDOW_PERMISSION:
+ return "SYSTEM_ALERT_WINDOW_PERMISSION";
+ case REASON_DEVICE_DEMO_MODE:
+ return "DEVICE_DEMO_MODE";
+ case REASON_ALLOWLISTED_PACKAGE:
+ return "ALLOWLISTED_PACKAGE";
+ case REASON_APPOP:
+ return "APPOP";
+ case REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD:
+ return "ACTIVITY_VISIBILITY_GRACE_PERIOD";
+ case REASON_OP_ACTIVATE_VPN:
+ return "OP_ACTIVATE_VPN";
+ case REASON_OP_ACTIVATE_PLATFORM_VPN:
+ return "OP_ACTIVATE_PLATFORM_VPN";
+ case REASON_CURRENT_INPUT_METHOD:
+ return "CURRENT_INPUT_METHOD";
+ case REASON_TEMP_ALLOWED_WHILE_IN_USE:
+ return "TEMP_ALLOWED_WHILE_IN_USE";
+ case REASON_GEOFENCING:
+ return "GEOFENCING";
+ case REASON_PUSH_MESSAGING:
+ return "PUSH_MESSAGING";
+ case REASON_PUSH_MESSAGING_OVER_QUOTA:
+ return "PUSH_MESSAGING_OVER_QUOTA";
+ case REASON_ACTIVITY_RECOGNITION:
+ return "ACTIVITY_RECOGNITION";
+ case REASON_ACCOUNT_TRANSFER:
+ return "REASON_ACCOUNT_TRANSFER";
+ case REASON_PUSH_MESSAGING_DEFERRABLE:
+ return "PUSH_MESSAGING_DEFERRABLE";
+ case REASON_BOOT_COMPLETED:
+ return "BOOT_COMPLETED";
+ case REASON_PRE_BOOT_COMPLETED:
+ return "PRE_BOOT_COMPLETED";
+ case REASON_LOCKED_BOOT_COMPLETED:
+ return "LOCKED_BOOT_COMPLETED";
+ case REASON_BLUETOOTH_BROADCAST:
+ return "BLUETOOTH_BROADCAST";
+ case REASON_TIMEZONE_CHANGED:
+ return "TIMEZONE_CHANGED";
+ case REASON_TIME_CHANGED:
+ return "TIME_CHANGED";
+ case REASON_LOCALE_CHANGED:
+ return "LOCALE_CHANGED";
+ case REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
+ return "REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
+ case REASON_REFRESH_SAFETY_SOURCES:
+ return "REASON_REFRESH_SAFETY_SOURCES";
+ case REASON_SYSTEM_ALLOW_LISTED:
+ return "SYSTEM_ALLOW_LISTED";
+ case REASON_ALARM_MANAGER_ALARM_CLOCK:
+ return "ALARM_MANAGER_ALARM_CLOCK";
+ case REASON_ALARM_MANAGER_WHILE_IDLE:
+ return "ALARM_MANAGER_WHILE_IDLE";
+ case REASON_SERVICE_LAUNCH:
+ return "SERVICE_LAUNCH";
+ case REASON_KEY_CHAIN:
+ return "KEY_CHAIN";
+ case REASON_PACKAGE_VERIFIER:
+ return "PACKAGE_VERIFIER";
+ case REASON_SYNC_MANAGER:
+ return "SYNC_MANAGER";
+ case REASON_DOMAIN_VERIFICATION_V1:
+ return "DOMAIN_VERIFICATION_V1";
+ case REASON_DOMAIN_VERIFICATION_V2:
+ return "DOMAIN_VERIFICATION_V2";
+ case REASON_VPN:
+ return "VPN";
+ case REASON_NOTIFICATION_SERVICE:
+ return "NOTIFICATION_SERVICE";
+ case REASON_PACKAGE_REPLACED:
+ return "PACKAGE_REPLACED";
+ case REASON_LOCATION_PROVIDER:
+ return "LOCATION_PROVIDER";
+ case REASON_MEDIA_BUTTON:
+ return "MEDIA_BUTTON";
+ case REASON_EVENT_SMS:
+ return "EVENT_SMS";
+ case REASON_EVENT_MMS:
+ return "EVENT_MMS";
+ case REASON_SHELL:
+ return "SHELL";
+ case REASON_MEDIA_SESSION_CALLBACK:
+ return "MEDIA_SESSION_CALLBACK";
+ case REASON_ROLE_DIALER:
+ return "ROLE_DIALER";
+ case REASON_ROLE_EMERGENCY:
+ return "ROLE_EMERGENCY";
+ case REASON_SYSTEM_MODULE:
+ return "SYSTEM_MODULE";
+ case REASON_CARRIER_PRIVILEGED_APP:
+ return "CARRIER_PRIVILEGED_APP";
+ case REASON_DPO_PROTECTED_APP:
+ return "DPO_PROTECTED_APP";
+ case REASON_DISALLOW_APPS_CONTROL:
+ return "DISALLOW_APPS_CONTROL";
+ case REASON_ACTIVE_DEVICE_ADMIN:
+ return "ACTIVE_DEVICE_ADMIN";
+ case REASON_OPT_OUT_REQUESTED:
+ return "REASON_OPT_OUT_REQUESTED";
+ case REASON_MEDIA_NOTIFICATION_TRANSFER:
+ return "REASON_MEDIA_NOTIFICATION_TRANSFER";
+ case REASON_PACKAGE_INSTALLER:
+ return "REASON_PACKAGE_INSTALLER";
+ default:
+ return "(unknown:" + reasonCode + ")";
+ }
+ }
+}
diff --git a/android-34/android/os/PowerManager.java b/android-34/android/os/PowerManager.java
new file mode 100644
index 0000000..d1063f6
--- /dev/null
+++ b/android-34/android/os/PowerManager.java
@@ -0,0 +1,3990 @@
+/*
+ * 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.CurrentTimeMillisLong;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+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.app.PropertyInvalidatedCache;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.service.dreams.Sandman;
+import android.sysprop.InitProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This class lets you query and request control of aspects of the device's power state.
+ */
+@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/proto_logging/stats/enums/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>
+ * This flag requires {@link android.Manifest.permission#TURN_SCREEN_ON} for apps targeting
+ * Android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and higher.
+ * </p><p>
+ * Normally wake locks don't actually wake the device, they just cause the screen to remain on
+ * once it's already on. This flag will cause the device to wake up when the wake lock is
+ * acquired.
+ * </p><p>
+ * Android TV playback devices attempt to turn on the HDMI-connected TV via HDMI-CEC on any
+ * wake-up, including wake-ups triggered by wake locks.
+ * </p><p>
+ * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
+ * </p>
+ *
+ * @deprecated Most applications should use {@link android.R.attr#turnScreenOn} or
+ * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the previous
+ * foreground app from being resumed first when the screen turns on.
+ */
+ @Deprecated
+ @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
+ 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>
+ * This will not turn the screen on if it is not already on.
+ * </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;
+
+ /**
+ * Wake lock flag: This wake lock should be held by the system.
+ *
+ * <p>Meant to allow tests to keep the device awake even when power restrictions are active.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public static final int SYSTEM_WAKELOCK = 0x80000000;
+
+ /**
+ * 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;
+
+ /**
+ * Brightness value for an invalid value having been stored.
+ * @hide
+ */
+ public static final int BRIGHTNESS_INVALID = -1;
+
+ //Brightness values for new float implementation:
+ /**
+ * Brightness value for fully on as float.
+ * @hide
+ */
+ public static final float BRIGHTNESS_MAX = 1.0f;
+
+ /**
+ * Brightness value for minimum valid brightness as float.
+ * @hide
+ */
+ public static final float BRIGHTNESS_MIN = 0.0f;
+
+ /**
+ * Brightness value for fully off in float.
+ * @hide
+ */
+ public static final float BRIGHTNESS_OFF_FLOAT = -1.0f;
+
+ /**
+ * Invalid brightness value.
+ * @hide
+ */
+ public static final float BRIGHTNESS_INVALID_FLOAT = Float.NaN;
+
+ // 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 event type: {@link com.android.server.power.FaceDownDetector} taking action
+ * on behalf of user.
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_FACE_DOWN = 5;
+
+ /**
+ * User activity event type: There is a change in the device state.
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+ USER_ACTIVITY_EVENT_OTHER,
+ USER_ACTIVITY_EVENT_BUTTON,
+ USER_ACTIVITY_EVENT_TOUCH,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY,
+ USER_ACTIVITY_EVENT_ATTENTION,
+ USER_ACTIVITY_EVENT_FACE_DOWN,
+ USER_ACTIVITY_EVENT_DEVICE_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActivityEvent{}
+
+ /**
+ *
+ * Convert the user activity event to a string for debugging purposes.
+ * @hide
+ */
+ public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+ switch (userActivityEvent) {
+ case USER_ACTIVITY_EVENT_OTHER: return "other";
+ case USER_ACTIVITY_EVENT_BUTTON: return "button";
+ case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+ case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+ case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+ case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+ case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+ default: return Integer.toString(userActivityEvent);
+ }
+ }
+
+ /**
+ * 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;
+
+ /**
+ * Go to sleep reason code: Going to sleep due to user inattentiveness.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_INATTENTIVE = 9;
+
+ /**
+ * Go to sleep reason code: Going to sleep due to quiescent boot.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_QUIESCENT = 10;
+
+ /**
+ * Go to sleep reason code: The last powered on display group has been removed.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED = 11;
+
+ /**
+ * Go to sleep reason code: Every display group has been turned off.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF = 12;
+
+ /**
+ * Go to sleep reason code: A foldable device has been folded.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_DEVICE_FOLD = 13;
+
+ /**
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DEVICE_FOLD;
+
+ /**
+ * @hide
+ */
+ public static String sleepReasonToString(@GoToSleepReason int sleepReason) {
+ switch (sleepReason) {
+ case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility";
+ case GO_TO_SLEEP_REASON_APPLICATION: return "application";
+ case GO_TO_SLEEP_REASON_DEVICE_ADMIN: return "device_admin";
+ case GO_TO_SLEEP_REASON_DEVICE_FOLD: return "device_folded";
+ case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed";
+ case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off";
+ case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend";
+ case GO_TO_SLEEP_REASON_HDMI: return "hdmi";
+ case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive";
+ 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_QUIESCENT: return "quiescent";
+ case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
+ case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout";
+ 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;
+
+ /**
+ * Go to sleep flag: Sleep softly, go to sleep only if there's no wakelock explicitly keeping
+ * the device awake.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_FLAG_SOFT_SLEEP = 1 << 1;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "BRIGHTNESS_CONSTRAINT_TYPE" }, value = {
+ BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM,
+ BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM,
+ BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT,
+ BRIGHTNESS_CONSTRAINT_TYPE_DIM,
+ BRIGHTNESS_CONSTRAINT_TYPE_DOZE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BrightnessConstraint{}
+
+ /**
+ * Brightness constraint type: minimum allowed value.
+ * @hide
+ */
+ public static final int BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM = 0;
+ /**
+ * Brightness constraint type: minimum allowed value.
+ * @hide
+ */
+ public static final int BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM = 1;
+
+ /**
+ * Brightness constraint type: minimum allowed value.
+ * @hide
+ */
+ public static final int BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT = 2;
+
+ /**
+ * Brightness constraint type: minimum allowed value.
+ * @hide
+ */
+ public static final int BRIGHTNESS_CONSTRAINT_TYPE_DIM = 3;
+
+ /**
+ * Brightness constraint type: minimum allowed value.
+ * @hide
+ */
+ public static final int BRIGHTNESS_CONSTRAINT_TYPE_DOZE = 4;
+
+ /**
+ * @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,
+ WAKE_REASON_DISPLAY_GROUP_ADDED,
+ WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
+ WAKE_REASON_UNFOLD_DEVICE,
+ WAKE_REASON_DREAM_FINISHED,
+ WAKE_REASON_TILT,
+ WAKE_REASON_TAP,
+ WAKE_REASON_LIFT,
+ WAKE_REASON_BIOMETRIC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WakeReason{}
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "GO_TO_SLEEP_REASON_" }, value = {
+ GO_TO_SLEEP_REASON_ACCESSIBILITY,
+ GO_TO_SLEEP_REASON_APPLICATION,
+ GO_TO_SLEEP_REASON_DEVICE_ADMIN,
+ GO_TO_SLEEP_REASON_DEVICE_FOLD,
+ GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED,
+ GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF,
+ GO_TO_SLEEP_REASON_FORCE_SUSPEND,
+ GO_TO_SLEEP_REASON_HDMI,
+ GO_TO_SLEEP_REASON_INATTENTIVE,
+ GO_TO_SLEEP_REASON_LID_SWITCH,
+ GO_TO_SLEEP_REASON_POWER_BUTTON,
+ GO_TO_SLEEP_REASON_QUIESCENT,
+ GO_TO_SLEEP_REASON_SLEEP_BUTTON,
+ GO_TO_SLEEP_REASON_TIMEOUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GoToSleepReason{}
+
+ /**
+ * 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. This includes user
+ * interactions with UI on the screen such as the notification shade. This does not include
+ * {@link WAKE_REASON_TAP} or {@link WAKE_REASON_LIFT}.
+ * @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;
+
+ /**
+ * Wake up reason code: Waking due to display group being added.
+ * @hide
+ */
+ public static final int WAKE_REASON_DISPLAY_GROUP_ADDED = 10;
+
+ /**
+ * Wake up reason code: Waking due to display group being powered on.
+ * @hide
+ */
+ public static final int WAKE_REASON_DISPLAY_GROUP_TURNED_ON = 11;
+
+ /**
+ * Wake up reason code: Waking the device due to unfolding of a foldable device.
+ * @hide
+ */
+ public static final int WAKE_REASON_UNFOLD_DEVICE = 12;
+
+ /**
+ * Wake up reason code: Waking the device due to the dream finishing.
+ * @hide
+ */
+ public static final int WAKE_REASON_DREAM_FINISHED = 13;
+
+ /**
+ * Wake up reason code: Waking due to tilt.
+ * @hide
+ */
+ public static final int WAKE_REASON_TILT = 14;
+ /**
+ * Wake up reason code: Waking up due to the user single or double tapping on the screen. This
+ * wake reason is used when the user is not tapping on a specific UI element; rather, the device
+ * wakes up due to a generic tap on the screen.
+ * @hide
+ */
+ public static final int WAKE_REASON_TAP = 15;
+
+ /**
+ * Wake up reason code: Waking up due to a user performed lift gesture.
+ * @hide
+ */
+ public static final int WAKE_REASON_LIFT = 16;
+
+ /**
+ * Wake up reason code: Waking up due to a user interacting with a biometric.
+ * @hide
+ */
+ public static final int WAKE_REASON_BIOMETRIC = 17;
+
+ /**
+ * 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";
+ case WAKE_REASON_DISPLAY_GROUP_ADDED: return "WAKE_REASON_DISPLAY_GROUP_ADDED";
+ case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON";
+ case WAKE_REASON_UNFOLD_DEVICE: return "WAKE_REASON_UNFOLD_DEVICE";
+ case WAKE_REASON_DREAM_FINISHED: return "WAKE_REASON_DREAM_FINISHED";
+ case WAKE_REASON_TILT: return "WAKE_REASON_TILT";
+ case WAKE_REASON_TAP: return "WAKE_REASON_TAP";
+ case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT";
+ case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC";
+ default: return Integer.toString(wakeReason);
+ }
+ }
+
+ /**
+ * Information related to the device waking up, triggered by {@link #wakeUp}.
+ *
+ * @hide
+ */
+ public static class WakeData {
+ public WakeData(long wakeTime, @WakeReason int wakeReason, long sleepDurationRealtime) {
+ this.wakeTime = wakeTime;
+ this.wakeReason = wakeReason;
+ this.sleepDurationRealtime = sleepDurationRealtime;
+ }
+ public final long wakeTime;
+ public final @WakeReason int wakeReason;
+ public final long sleepDurationRealtime;
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof WakeData) {
+ final WakeData other = (WakeData) o;
+ return wakeTime == other.wakeTime && wakeReason == other.wakeReason
+ && sleepDurationRealtime == other.sleepDurationRealtime;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(wakeTime, wakeReason, sleepDurationRealtime);
+ }
+ }
+
+ /**
+ * Information related to the device going to sleep, triggered by {@link #goToSleep}.
+ *
+ * @hide
+ */
+ public static class SleepData {
+ public SleepData(long goToSleepUptimeMillis, @GoToSleepReason int goToSleepReason) {
+ this.goToSleepUptimeMillis = goToSleepUptimeMillis;
+ this.goToSleepReason = goToSleepReason;
+ }
+ public final long goToSleepUptimeMillis;
+ public final @GoToSleepReason int goToSleepReason;
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof SleepData) {
+ final SleepData other = (SleepData) o;
+ return goToSleepUptimeMillis == other.goToSleepUptimeMillis
+ && goToSleepReason == other.goToSleepReason;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(goToSleepUptimeMillis, goToSleepReason);
+ }
+ }
+
+ /**
+ * 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 for rebooting userspace.
+ * @hide
+ */
+ @SystemApi
+ public static final String REBOOT_USERSPACE = "userspace";
+
+ /**
+ * 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 {}
+
+ /**
+ * In this mode, all active SoundTrigger recognitions are enabled by the SoundTrigger system
+ * service.
+ * @hide
+ */
+ @SystemApi
+ public static final int SOUND_TRIGGER_MODE_ALL_ENABLED = 0;
+ /**
+ * In this mode, only privileged components of the SoundTrigger system service should be
+ * enabled. This functionality is to be used to limit SoundTrigger recognitions to those only
+ * deemed necessary by the system.
+ * @hide
+ */
+ @SystemApi
+ public static final int SOUND_TRIGGER_MODE_CRITICAL_ONLY = 1;
+ /**
+ * In this mode, all active SoundTrigger recognitions should be disabled by the SoundTrigger
+ * system service.
+ * @hide
+ */
+ @SystemApi
+ public static final int SOUND_TRIGGER_MODE_ALL_DISABLED = 2;
+
+ /** @hide */
+ public static final int MIN_SOUND_TRIGGER_MODE = SOUND_TRIGGER_MODE_ALL_ENABLED;
+ /** @hide */
+ public static final int MAX_SOUND_TRIGGER_MODE = SOUND_TRIGGER_MODE_ALL_DISABLED;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SOUND_TRIGGER_MODE_"}, value = {
+ SOUND_TRIGGER_MODE_ALL_ENABLED,
+ SOUND_TRIGGER_MODE_CRITICAL_ONLY,
+ SOUND_TRIGGER_MODE_ALL_DISABLED,
+ })
+ public @interface SoundTriggerPowerSaveMode {}
+
+ /** @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);
+ }
+ }
+
+ private static final String CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY =
+ "cache_key.is_power_save_mode";
+
+ private static final String CACHE_KEY_IS_INTERACTIVE_PROPERTY = "cache_key.is_interactive";
+
+ private static final int MAX_CACHE_ENTRIES = 1;
+
+ private final PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
+ new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
+ CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY) {
+ @Override
+ public Boolean recompute(Void query) {
+ try {
+ return mService.isPowerSaveMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ };
+
+ private final PropertyInvalidatedCache<Integer, Boolean> mInteractiveCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(MAX_CACHE_ENTRIES,
+ CACHE_KEY_IS_INTERACTIVE_PROPERTY) {
+ @Override
+ public Boolean recompute(Integer displayId) {
+ try {
+ if (displayId == null) {
+ return mService.isInteractive();
+ } else {
+ return mService.isDisplayInteractive(displayId);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ };
+
+ final Context mContext;
+ @UnsupportedAppUsage
+ final IPowerManager mService;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ final Handler mHandler;
+ final IThermalService mThermalService;
+
+ /** We lazily initialize it.*/
+ private PowerExemptionManager mPowerExemptionManager;
+
+ private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
+ mListenerMap = new ArrayMap<>();
+
+ /**
+ * {@hide}
+ */
+ public PowerManager(Context context, IPowerManager service, IThermalService thermalService,
+ Handler handler) {
+ mContext = context;
+ mService = service;
+ mThermalService = thermalService;
+ mHandler = handler;
+ }
+
+ private PowerExemptionManager getPowerExemptionManager() {
+ if (mPowerExemptionManager == null) {
+ // No need for synchronization; getSystemService() will return the same object anyway.
+ mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
+ }
+ return mPowerExemptionManager;
+ }
+
+ /**
+ * 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 a float screen brightness setting.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public float getBrightnessConstraint(int constraint) {
+ try {
+ return mService.getBrightnessConstraint(constraint);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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 = mContext.getSystemService(PowerManager.class);
+ * 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>
+ * <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 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.
+ * Additionally using the flag will keep only the appropriate screen on in a
+ * multi-display scenario while using a wake lock will keep every screen powered on.
+ * </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 personally 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(),
+ Display.INVALID_DISPLAY);
+ }
+
+ /**
+ * Creates a new wake lock with the specified level and flags.
+ * <p>
+ * The wakelock will only apply to the {@link com.android.server.display.DisplayGroup} of the
+ * provided {@code displayId}. If {@code displayId} is {@link Display#INVALID_DISPLAY} then it
+ * will apply to all {@link com.android.server.display.DisplayGroup DisplayGroups}.
+ *
+ * @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.
+ * @param displayId The display id to which this wake lock is tied.
+ *
+ * @hide
+ */
+ public WakeLock newWakeLock(int levelAndFlags, String tag, int displayId) {
+ validateWakeLockParameters(levelAndFlags, tag);
+ return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(), displayId);
+ }
+
+ /** @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(mContext.getDisplayId(), when, event, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
+ * to turn off.
+ *
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
+ * turned on it will be turned off. If all displays are off as a result of this action the
+ * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
+ * default display group} is already off then nothing will happen.
+ *
+ * <p>If the device is an Android TV playback device and the current active source on the
+ * HDMI-connected TV, it will attempt to turn off that TV via HDMI-CEC.
+ *
+ * <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 {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
+ * to turn off.
+ *
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
+ * turned on it will be turned off. If all displays are off as a result of this action the
+ * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
+ * default display group} is already off then nothing will happen.
+ *
+ * <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 {@link com.android.server.display.DisplayGroup} of the provided {@code displayId}
+ * to turn off.
+ *
+ * <p>If the {@link com.android.server.display.DisplayGroup} of the provided {@code displayId}
+ * is turned on it will be turned off. If all displays are off as a result of this action the
+ * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup} of
+ * the provided {@code displayId} is already off then nothing will happen.
+ *
+ * <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>Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ *
+ * @param displayId The display ID to turn off. If {@code displayId} is
+ * {@link Display#INVALID_DISPLAY}, then all displays are turned off.
+ * @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.
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void goToSleep(int displayId, long time, @GoToSleepReason int reason, int flags) {
+ try {
+ mService.goToSleepWithDisplayId(displayId, time, reason, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
+ * to turn on.
+ *
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
+ * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
+ * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
+ * on then nothing will happen.
+ *
+ * <p>
+ * 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 {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
+ * to turn on.
+ *
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
+ * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
+ * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
+ * on then nothing will happen.
+ *
+ * <p>
+ * 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 {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on.
+ *
+ * <p>If the {@link android.view.Display#DEFAULT_DISPLAY default display} is turned off it will
+ * be turned on. Additionally, if the device is asleep it will be awoken. If the {@link
+ * android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen.
+ *
+ * <p>If the device is an Android TV playback device, it will attempt to turn on the
+ * HDMI-connected TV and become the current active source via the HDMI-CEC One Touch Play
+ * feature.
+ *
+ * <p>
+ * 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 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() {
+ return mInteractiveCache.query(null);
+ }
+
+ /**
+ * Returns the interactive state for a specific display, which may not be the same as the
+ * global wakefulness (which is true when any display is awake).
+ *
+ * @param displayId
+ * @return whether the given display is present and interactive, or false
+ *
+ * @hide
+ */
+ public boolean isInteractive(int displayId) {
+ return mInteractiveCache.query(displayId);
+ }
+
+ /**
+ * Returns {@code true} if this device supports rebooting userspace.
+ *
+ * <p>This method exists solely for the sake of re-using same logic between {@code PowerManager}
+ * and {@code PowerManagerService}.
+ *
+ * @hide
+ */
+ public static boolean isRebootingUserspaceSupportedImpl() {
+ return InitProperties.is_userspace_reboot_supported().orElse(false);
+ }
+
+ /**
+ * Returns {@code true} if this device supports rebooting userspace.
+ */
+ // TODO(b/138605180): add link to documentation once it's ready.
+ public boolean isRebootingUserspaceSupported() {
+ return isRebootingUserspaceSupportedImpl();
+ }
+
+ /**
+ * Reboot the device. Will not return if the reboot is successful.
+ * <p>
+ * Requires the {@link android.Manifest.permission#REBOOT} permission.
+ * </p>
+ * <p>
+ * If the {@code reason} string contains ",quiescent", then the screen stays off during reboot
+ * and is not turned on again until the user triggers the device to wake up (for example,
+ * by pressing the power key).
+ * This behavior applies to Android TV devices launched on Android 11 (API level 30) or higher.
+ * </p>
+ *
+ * @param reason code to pass to the kernel (e.g., "recovery") to
+ * request special boot modes, or null.
+ * @throws UnsupportedOperationException if userspace reboot was requested on a device that
+ * doesn't support it.
+ */
+ @RequiresPermission(permission.REBOOT)
+ public void reboot(@Nullable String reason) {
+ if (REBOOT_USERSPACE.equals(reason) && !isRebootingUserspaceSupported()) {
+ throw new UnsupportedOperationException(
+ "Attempted userspace reboot on a device that doesn't support it");
+ }
+ 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
+ */
+ @RequiresPermission(permission.REBOOT)
+ public void rebootSafeMode() {
+ try {
+ mService.rebootSafeMode(false, true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if the platform has auto power save modes (eg. Doze & app standby) enabled.
+ * This doesn't necessarily mean that the individual features are enabled. For example, if this
+ * returns true, Doze might be enabled while app standby buckets remain disabled.
+ * @hide
+ */
+ @TestApi
+ public boolean areAutoPowerSaveModesEnabled() {
+ try {
+ return mService.areAutoPowerSaveModesEnabled();
+ } 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() {
+ return mPowerSaveModeCache.query(null);
+ }
+
+ /**
+ * Set the current power save mode.
+ *
+ * @return True if the set was allowed.
+ *
+ * @hide
+ * @see #isPowerSaveMode()
+ */
+ @SystemApi
+ @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();
+ }
+ }
+
+ /**
+ * Gets the current policy for full power save mode.
+ *
+ * @return The {@link BatterySaverPolicyConfig} which is currently set for the full power save
+ * policy level.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public BatterySaverPolicyConfig getFullPowerSavePolicy() {
+ try {
+ return mService.getFullPowerSavePolicy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the policy for full power save mode.
+ *
+ * Any settings set by this API will persist for only one session of full battery saver mode.
+ * The settings set by this API are cleared upon exit of full battery saver mode, and the
+ * caller is expected to set the desired values again for the next full battery saver mode
+ * session if desired.
+ *
+ * Use-cases:
+ * 1. Set policy outside of full battery saver mode
+ * - full policy set -> enter BS -> policy setting applied -> exit BS -> setting cleared
+ * 2. Set policy inside of full battery saver mode
+ * - enter BS -> full policy set -> policy setting applied -> exit BS -> setting cleared
+ *
+ * This API is intended to be used with {@link #getFullPowerSavePolicy()} API when a client only
+ * wants to modify a specific setting(s) and leave the remaining policy attributes the same.
+ * Example:
+ * BatterySaverPolicyConfig newFullPolicyConfig =
+ * new BatterySaverPolicyConfig.Builder(powerManager.getFullPowerSavePolicy())
+ * .setSoundTriggerMode(PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED)
+ * .build();
+ * powerManager.setFullPowerSavePolicy(newFullPolicyConfig);
+ *
+ * @return true if there was an effectual change. If full battery saver is enabled, then this
+ * will return true.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.DEVICE_POWER,
+ android.Manifest.permission.POWER_SAVER
+ })
+ public boolean setFullPowerSavePolicy(@NonNull BatterySaverPolicyConfig config) {
+ try {
+ return mService.setFullPowerSavePolicy(config);
+ } 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
+ @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
+ 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
+ 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.
+ *
+ * <p>Note: Prior to Android version {@link Build.VERSION_CODES#S}, any app calling this method
+ * was required to hold the {@link android.Manifest.permission#POWER_SAVER} permission. Starting
+ * from Android version {@link Build.VERSION_CODES#S}, that permission is no longer required.
+ *
+ * @return The current value power saver mode for the system.
+ *
+ * @see AutoPowerSaveModeTriggers
+ * @see PowerManager#getPowerSaveModeTrigger()
+ * @hide
+ */
+ @AutoPowerSaveModeTriggers
+ @SystemApi
+ public int getPowerSaveModeTrigger() {
+ try {
+ return mService.getPowerSaveModeTrigger();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allows an app to tell the system how long it believes the battery will last and whether
+ * this estimate is customized based on historical device usage or on a generic configuration.
+ * These estimates will be displayed on system UI surfaces in place of the system computed
+ * value.
+ *
+ * Calling this requires either the {@link android.Manifest.permission#DEVICE_POWER} or the
+ * {@link android.Manifest.permission#BATTERY_PREDICTION} permissions.
+ *
+ * @param timeRemaining The time remaining as a {@link Duration}.
+ * @param isPersonalized true if personalized based on device usage history, false otherwise.
+ * @throws IllegalStateException if the device is powered or currently charging
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.BATTERY_PREDICTION,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setBatteryDischargePrediction(@NonNull Duration timeRemaining,
+ boolean isPersonalized) {
+ if (timeRemaining == null) {
+ throw new IllegalArgumentException("time remaining must not be null");
+ }
+ try {
+ mService.setBatteryDischargePrediction(new ParcelDuration(timeRemaining),
+ isPersonalized);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current battery life remaining estimate.
+ *
+ * @return The estimated battery life remaining as a {@link Duration}. Will be {@code null} if
+ * the device is powered, charging, or an error was encountered.
+ */
+ @Nullable
+ public Duration getBatteryDischargePrediction() {
+ try {
+ final ParcelDuration parcelDuration = mService.getBatteryDischargePrediction();
+ if (parcelDuration == null) {
+ return null;
+ }
+ return parcelDuration.getDuration();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the current battery life remaining estimate is personalized based on device
+ * usage history or not. This value does not take a device's powered or charging state into
+ * account.
+ *
+ * @return A boolean indicating if the current discharge estimate is personalized based on
+ * historical device usage or not.
+ */
+ public boolean isBatteryDischargePredictionPersonalized() {
+ try {
+ return mService.isBatteryDischargePredictionPersonalized();
+ } 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 how SoundTrigger features should behave when battery saver is on. When battery saver
+ * is off, this will always return {@link #SOUND_TRIGGER_MODE_ALL_ENABLED}.
+ *
+ * <p>This API is normally only useful for components that provide use SoundTrigger features.
+ *
+ * @see #isPowerSaveMode()
+ * @see #ACTION_POWER_SAVE_MODE_CHANGED
+ *
+ * @hide
+ */
+ @SoundTriggerPowerSaveMode
+ public int getSoundTriggerPowerSaveMode() {
+ final PowerSaveState powerSaveState = getPowerSaveState(ServiceType.SOUND);
+ if (!powerSaveState.batterySaverEnabled) {
+ return SOUND_TRIGGER_MODE_ALL_ENABLED;
+ }
+ return powerSaveState.soundTriggerMode;
+ }
+
+ /**
+ * 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_DEVICE_LIGHT_IDLE_MODE_CHANGED}.
+ *
+ * @return Returns true if currently in active device light 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.
+ */
+ public boolean isDeviceLightIdleMode() {
+ try {
+ return mService.isLightDeviceIdleMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #isDeviceLightIdleMode()
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S,
+ publicAlternatives = "Use {@link #isDeviceLightIdleMode()} instead.")
+ public boolean isLightDeviceIdleMode() {
+ return isDeviceLightIdleMode();
+ }
+
+ /**
+ * Returns true if Low Power Standby is supported on this device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public boolean isLowPowerStandbySupported() {
+ try {
+ return mService.isLowPowerStandbySupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if Low Power Standby is enabled.
+ *
+ * <p>When Low Power Standby is enabled, apps (including apps running foreground services) are
+ * subject to additional restrictions while the device is non-interactive, outside of device
+ * idle maintenance windows: Their network access is disabled, and any wakelocks they hold are
+ * ignored.
+ *
+ * <p>When Low Power Standby is enabled or disabled, a Intent with action
+ * {@link #ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED} is broadcast to registered receivers.
+ */
+ public boolean isLowPowerStandbyEnabled() {
+ try {
+ return mService.isLowPowerStandbyEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether Low Power Standby is enabled.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @see #isLowPowerStandbyEnabled()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyEnabled(boolean enabled) {
+ try {
+ mService.setLowPowerStandbyEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether Low Power Standby should be active during doze maintenance mode.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @see #isLowPowerStandbyEnabled()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ try {
+ mService.setLowPowerStandbyActiveDuringMaintenance(activeDuringMaintenance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force Low Power Standby restrictions to be active.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void forceLowPowerStandbyActive(boolean active) {
+ try {
+ mService.forceLowPowerStandbyActive(active);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the current Low Power Standby policy.
+ *
+ * When the policy changes {@link #ACTION_LOW_POWER_STANDBY_POLICY_CHANGED} is broadcast to
+ * registered receivers.
+ *
+ * @param policy The policy to set. If null, resets to the default policy.
+ * @see #getLowPowerStandbyPolicy
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyPolicy(@Nullable LowPowerStandbyPolicy policy) {
+ try {
+ mService.setLowPowerStandbyPolicy(LowPowerStandbyPolicy.toParcelable(policy));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the current Low Power Standby policy.
+ *
+ * When the policy changes {@link #ACTION_LOW_POWER_STANDBY_POLICY_CHANGED} is broadcast to
+ * registered receivers.
+ *
+ * @see #setLowPowerStandbyPolicy
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public LowPowerStandbyPolicy getLowPowerStandbyPolicy() {
+ try {
+ return LowPowerStandbyPolicy.fromParcelable(mService.getLowPowerStandbyPolicy());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if the calling package is exempt from Low Power Standby restrictions or
+ * Low Power Standby is disabled (so Low Power Standby does not restrict apps),
+ * false otherwise.
+ */
+ public boolean isExemptFromLowPowerStandby() {
+ try {
+ return mService.isExemptFromLowPowerStandby();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if Low Power Standby is disabled (so Low Power Standby does not restrict apps),
+ * or apps may be automatically exempt from Low Power Standby restrictions for the given reason.
+ *
+ * The system may exempt apps from Low Power Standby restrictions when using allowed features.
+ * For example, if {@link #LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION} is allowed,
+ * then apps with active voice interaction sessions are exempt from restrictions.
+ */
+ public boolean isAllowedInLowPowerStandby(@LowPowerStandbyAllowedReason int reason) {
+ try {
+ return mService.isReasonAllowedInLowPowerStandby(reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if Low Power Standby is disabled (so Low Power Standby does not restrict apps),
+ * or apps are allowed to use a given feature during Low Power Standby.
+ */
+ public boolean isAllowedInLowPowerStandby(@NonNull String feature) {
+ try {
+ return mService.isFeatureAllowedInLowPowerStandby(feature);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a new Low Power Standby ports lock.
+ *
+ * <p>A Low Power Standby ports lock requests that the given ports remain open during
+ * Low Power Standby.
+ * Call {@link LowPowerStandbyPortsLock#acquire} to acquire the lock.
+ * This request is only respected if the calling package is exempt
+ * (see {@link #isExemptFromLowPowerStandby()}), and until the returned
+ * {@code LowPowerStandbyPorts} object is destroyed or has
+ * {@link LowPowerStandbyPortsLock#release} called on it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+ @NonNull
+ public LowPowerStandbyPortsLock newLowPowerStandbyPortsLock(
+ @NonNull List<LowPowerStandbyPortDescription> ports) {
+ LowPowerStandbyPortsLock standbyPorts = new LowPowerStandbyPortsLock(ports);
+ return standbyPorts;
+ }
+
+ /**
+ * Gets all ports that should remain open in standby.
+ * Only includes ports requested by exempt packages (see {@link #getLowPowerStandbyPolicy()}).
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ @NonNull
+ public List<LowPowerStandbyPortDescription> getActiveLowPowerStandbyPorts() {
+ try {
+ return LowPowerStandbyPortDescription.fromParcelable(
+ mService.getActiveLowPowerStandbyPorts());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether the given application package name is on the device's power allowlist.
+ * Apps can be placed on the allowlist through the settings UI invoked by
+ * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
+ * <p>Being on the power allowlist means that the system will not apply most power saving
+ * features to the app. Guardrails for extreme cases may still be applied.
+ */
+ public boolean isIgnoringBatteryOptimizations(String packageName) {
+ return getPowerExemptionManager().isAllowListed(packageName, true);
+ }
+
+ /**
+ * 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() {
+ 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) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ 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) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Preconditions.checkArgument(!mListenerMap.containsKey(listener),
+ "Listener already registered: %s", 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) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ 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();
+ }
+ }
+
+ @CurrentTimeMillisLong
+ private final AtomicLong mLastHeadroomUpdate = new AtomicLong(0L);
+ private static final int MINIMUM_HEADROOM_TIME_MILLIS = 500;
+
+ /**
+ * Provides an estimate of how much thermal headroom the device currently has before hitting
+ * severe throttling.
+ *
+ * Note that this only attempts to track the headroom of slow-moving sensors, such as the skin
+ * temperature sensor. This means that there is no benefit to calling this function more
+ * frequently than about once per second, and attempts to call significantly more frequently may
+ * result in the function returning {@code NaN}.
+ * <p>
+ * In addition, in order to be able to provide an accurate forecast, the system does not attempt
+ * to forecast until it has multiple temperature samples from which to extrapolate. This should
+ * only take a few seconds from the time of the first call, but during this time, no forecasting
+ * will occur, and the current headroom will be returned regardless of the value of
+ * {@code forecastSeconds}.
+ * <p>
+ * The value returned is a non-negative float that represents how much of the thermal envelope
+ * is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is (or
+ * will be) throttled at {@link #THERMAL_STATUS_SEVERE}. Such throttling can affect the CPU,
+ * GPU, and other subsystems. Values may exceed 1.0, but there is no implied mapping to specific
+ * thermal status levels beyond that point. This means that values greater than 1.0 may
+ * correspond to {@link #THERMAL_STATUS_SEVERE}, but may also represent heavier throttling.
+ * <p>
+ * A value of 0.0 corresponds to a fixed distance from 1.0, but does not correspond to any
+ * particular thermal status or temperature. Values on (0.0, 1.0] may be expected to scale
+ * linearly with temperature, though temperature changes over time are typically not linear.
+ * Negative values will be clamped to 0.0 before returning.
+ *
+ * @param forecastSeconds how many seconds in the future to forecast. Given that device
+ * conditions may change at any time, forecasts from further in the
+ * future will likely be less accurate than forecasts in the near future.
+ * @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling
+ * threshold, as described above. Returns NaN if the device does not support this
+ * functionality or if this function is called significantly faster than once per
+ * second.
+ */
+ public float getThermalHeadroom(@IntRange(from = 0, to = 60) int forecastSeconds) {
+ // Rate-limit calls into the thermal service
+ long now = SystemClock.elapsedRealtime();
+ long timeSinceLastUpdate = now - mLastHeadroomUpdate.get();
+ if (timeSinceLastUpdate < MINIMUM_HEADROOM_TIME_MILLIS) {
+ return Float.NaN;
+ }
+
+ try {
+ float forecast = mThermalService.getThermalHeadroom(forecastSeconds);
+ mLastHeadroomUpdate.set(SystemClock.elapsedRealtime());
+ return forecast;
+ } 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 true if ambient display is available on the device.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+ public boolean isAmbientDisplayAvailable() {
+ try {
+ return mService.isAmbientDisplayAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * If true, suppresses the current ambient display configuration and disables ambient display.
+ *
+ * <p>This method has no effect if {@link #isAmbientDisplayAvailable()} is false.
+ *
+ * @param token A persistable identifier for the ambient display suppression that is unique
+ * within the calling application.
+ * @param suppress If set to {@code true}, ambient display will be suppressed. If set to
+ * {@code false}, ambient display will no longer be suppressed for the given
+ * token.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+ public void suppressAmbientDisplay(@NonNull String token, boolean suppress) {
+ try {
+ mService.suppressAmbientDisplay(token, suppress);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if ambient display is suppressed by the calling app with the given
+ * {@code token}.
+ *
+ * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false.
+ *
+ * @param token The identifier of the ambient display suppression.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+ public boolean isAmbientDisplaySuppressedForToken(@NonNull String token) {
+ try {
+ return mService.isAmbientDisplaySuppressedForToken(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if ambient display is suppressed by <em>any</em> app with <em>any</em> token.
+ *
+ * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+ public boolean isAmbientDisplaySuppressed() {
+ try {
+ return mService.isAmbientDisplaySuppressed();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if ambient display is suppressed by the given {@code appUid} with the given
+ * {@code token}.
+ *
+ * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false.
+ *
+ * @param token The identifier of the ambient display suppression.
+ * @param appUid The uid of the app that suppressed ambient display.
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_DREAM_STATE,
+ android.Manifest.permission.READ_DREAM_SUPPRESSION })
+ public boolean isAmbientDisplaySuppressedForTokenByApp(@NonNull String token, int appUid) {
+ try {
+ return mService.isAmbientDisplaySuppressedForTokenByApp(token, appUid);
+ } 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
+ */
+ @GoToSleepReason
+ 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 enhanced battery discharge prediction changes. The new
+ * value can be retrieved via {@link #getBatteryDischargePrediction()}.
+ * This broadcast is only sent to registered receivers.
+ *
+ * @hide
+ */
+ @TestApi
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED =
+ "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
+
+ /**
+ * 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 #isDeviceLightIdleMode()} changes.
+ * This broadcast is only sent to registered receivers.
+ */
+ @SuppressLint("ActionValue") // Need to do "LIGHT_DEVICE_IDLE..." for legacy reasons
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED =
+ // Use the old string so we don't break legacy apps.
+ "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+
+ /**
+ * @see #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553,
+ publicAlternatives = "Use {@link #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED} instead")
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED =
+ ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
+
+ /**
+ * @hide Intent that is broadcast when the set of power save allowlist 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 allowlisted 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 Low Power Standby is enabled or disabled.
+ * This broadcast is only sent to registered receivers.
+ *
+ * @see #isLowPowerStandbyEnabled()
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED =
+ "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
+
+ /**
+ * Intent that is broadcast when Low Power Standby policy is changed.
+ * This broadcast is only sent to registered receivers.
+ *
+ * @see #isExemptFromLowPowerStandby()
+ * @see #isAllowedInLowPowerStandby(int)
+ * @see #isAllowedInLowPowerStandby(String)
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOW_POWER_STANDBY_POLICY_CHANGED =
+ "android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED";
+
+ /**
+ * Intent that is broadcast when Low Power Standby exempt ports change.
+ * This broadcast is only sent to registered receivers.
+ *
+ * @see #getActiveLowPowerStandbyPorts
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY)
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOW_POWER_STANDBY_PORTS_CHANGED =
+ "android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED";
+
+ /**
+ * Signals that wake-on-lan/wake-on-wlan is allowed in Low Power Standby.
+ *
+ * <p>If Low Power Standby is enabled ({@link #isLowPowerStandbyEnabled()}),
+ * wake-on-lan/wake-on-wlan may not be available while in standby.
+ * Use {@link #isAllowedInLowPowerStandby(String)} to determine whether the device allows this
+ * feature to be used during Low Power Standby with the currently active Low Power Standby
+ * policy.
+ *
+ * @see #isAllowedInLowPowerStandby(String)
+ */
+ public static final String FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY =
+ "com.android.lowpowerstandby.WAKE_ON_LAN";
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "LOW_POWER_STANDBY_ALLOWED_REASON_" }, flag = true, value = {
+ LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION,
+ LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST,
+ LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LowPowerStandbyAllowedReason {
+ }
+
+ /**
+ * Exempts active Voice Interaction Sessions in Low Power Standby.
+ *
+ * @see #isAllowedInLowPowerStandby(int)
+ */
+ public static final int LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION = 1 << 0;
+
+ /**
+ * Exempts apps on the temporary powersave allowlist.
+ *
+ * @see #isAllowedInLowPowerStandby(int)
+ */
+ public static final int LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST = 1 << 1;
+
+ /**
+ * Exempts apps with ongoing calls.
+ *
+ * <p>This includes apps with foreground services of type "phoneCall".
+ *
+ * @see #isAllowedInLowPowerStandby(int)
+ */
+ public static final int LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL = 1 << 2;
+
+ /** @hide */
+ public static String lowPowerStandbyAllowedReasonsToString(
+ @LowPowerStandbyAllowedReason int allowedReasons) {
+ ArrayList<String> allowedStrings = new ArrayList<>();
+ if ((allowedReasons & LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION) != 0) {
+ allowedStrings.add("ALLOWED_REASON_VOICE_INTERACTION");
+ allowedReasons &= ~LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION;
+ }
+ if ((allowedReasons & LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST) != 0) {
+ allowedStrings.add("ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST");
+ allowedReasons &= ~LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
+ }
+ if ((allowedReasons & LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL) != 0) {
+ allowedStrings.add("ALLOWED_REASON_ONGOING_CALL");
+ allowedReasons &= ~LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL;
+ }
+ if (allowedReasons != 0) {
+ allowedStrings.add(String.valueOf(allowedReasons));
+ }
+ return String.join(",", allowedStrings);
+ }
+
+ /**
+ * Policy that defines the restrictions enforced by Low Power Standby.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class LowPowerStandbyPolicy {
+ /** Name of the policy, used for debugging & metrics */
+ @NonNull
+ private final String mIdentifier;
+
+ /** Packages that are exempt from Low Power Standby restrictions. */
+ @NonNull
+ private final Set<String> mExemptPackages;
+
+ /**
+ * Reasons that this policy allows apps to be automatically exempted
+ * from Low Power Standby restrictions for.
+ */
+ @LowPowerStandbyAllowedReason
+ private final int mAllowedReasons;
+
+ /**
+ * Features that are allowed to be used in Low Power Standby.
+ *
+ * @see #FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY
+ */
+ @NonNull
+ private final Set<String> mAllowedFeatures;
+
+ /**
+ * Create a policy that defines the restrictions enforced by Low Power Standby.
+ *
+ * @param identifier Name of the policy, used for debugging & metrics.
+ * @param exemptPackages Packages that are exempt from Low Power Standby restrictions.
+ * @param allowedReasons Reasons that this policy allows apps to be automatically exempted
+ * from Low Power Standby restrictions for.
+ * @param allowedFeatures Features that are allowed to be used in Low Power Standby.
+ * Features are declared as strings, see
+ * {@link #FEATURE_WAKE_ON_LAN_IN_LOW_POWER_STANDBY} as an example.
+ */
+ public LowPowerStandbyPolicy(@NonNull String identifier,
+ @NonNull Set<String> exemptPackages,
+ @LowPowerStandbyAllowedReason int allowedReasons,
+ @NonNull Set<String> allowedFeatures) {
+ Objects.requireNonNull(identifier);
+ Objects.requireNonNull(exemptPackages);
+ Objects.requireNonNull(allowedFeatures);
+
+ mIdentifier = identifier;
+ mExemptPackages = Collections.unmodifiableSet(exemptPackages);
+ mAllowedReasons = allowedReasons;
+ mAllowedFeatures = Collections.unmodifiableSet(allowedFeatures);
+ }
+
+ @NonNull
+ public String getIdentifier() {
+ return mIdentifier;
+ }
+
+ @NonNull
+ public Set<String> getExemptPackages() {
+ return mExemptPackages;
+ }
+
+ @LowPowerStandbyAllowedReason
+ public int getAllowedReasons() {
+ return mAllowedReasons;
+ }
+
+ @NonNull
+ public Set<String> getAllowedFeatures() {
+ return mAllowedFeatures;
+ }
+
+ @Override
+ public String toString() {
+ return "Policy{"
+ + "mIdentifier='" + mIdentifier + '\''
+ + ", mExemptPackages=" + String.join(",", mExemptPackages)
+ + ", mAllowedReasons=" + lowPowerStandbyAllowedReasonsToString(mAllowedReasons)
+ + ", mAllowedFeatures=" + String.join(",", mAllowedFeatures)
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof LowPowerStandbyPolicy)) return false;
+ LowPowerStandbyPolicy that = (LowPowerStandbyPolicy) o;
+ return mAllowedReasons == that.mAllowedReasons && Objects.equals(mIdentifier,
+ that.mIdentifier) && Objects.equals(mExemptPackages, that.mExemptPackages)
+ && Objects.equals(mAllowedFeatures, that.mAllowedFeatures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIdentifier, mExemptPackages, mAllowedReasons,
+ mAllowedFeatures);
+ }
+
+ /** @hide */
+ public static IPowerManager.LowPowerStandbyPolicy toParcelable(
+ LowPowerStandbyPolicy policy) {
+ if (policy == null) {
+ return null;
+ }
+
+ IPowerManager.LowPowerStandbyPolicy parcelablePolicy =
+ new IPowerManager.LowPowerStandbyPolicy();
+ parcelablePolicy.identifier = policy.mIdentifier;
+ parcelablePolicy.exemptPackages = new ArrayList<>(policy.mExemptPackages);
+ parcelablePolicy.allowedReasons = policy.mAllowedReasons;
+ parcelablePolicy.allowedFeatures = new ArrayList<>(policy.mAllowedFeatures);
+ return parcelablePolicy;
+ }
+
+ /** @hide */
+ public static LowPowerStandbyPolicy fromParcelable(
+ IPowerManager.LowPowerStandbyPolicy parcelablePolicy) {
+ if (parcelablePolicy == null) {
+ return null;
+ }
+
+ return new LowPowerStandbyPolicy(
+ parcelablePolicy.identifier,
+ new ArraySet<>(parcelablePolicy.exemptPackages),
+ parcelablePolicy.allowedReasons,
+ new ArraySet<>(parcelablePolicy.allowedFeatures));
+ }
+ }
+
+ /**
+ * Describes ports that may be requested to remain open during Low Power Standby.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class LowPowerStandbyPortDescription {
+ /** @hide */
+ @IntDef(prefix = { "PROTOCOL_" }, value = {
+ PROTOCOL_TCP,
+ PROTOCOL_UDP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Protocol {
+ }
+
+ /**
+ * Constant to indicate the {@link LowPowerStandbyPortDescription} refers to a TCP port.
+ */
+ public static final int PROTOCOL_TCP = 6;
+ /**
+ * Constant to indicate the {@link LowPowerStandbyPortDescription} refers to a UDP port.
+ */
+ public static final int PROTOCOL_UDP = 17;
+
+ /** @hide */
+ @IntDef(prefix = { "MATCH_PORT_" }, value = {
+ MATCH_PORT_LOCAL,
+ MATCH_PORT_REMOTE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PortMatcher {
+ }
+ /**
+ * Constant to indicate the {@link LowPowerStandbyPortDescription}'s port number is to be
+ * matched against the socket's local port number (the destination port number of an
+ * incoming packet).
+ */
+ public static final int MATCH_PORT_LOCAL = 1;
+ /**
+ * Constant to indicate the {@link LowPowerStandbyPortDescription}'s port number is to be
+ * matched against the socket's remote port number (the source port number of an
+ * incoming packet).
+ */
+ public static final int MATCH_PORT_REMOTE = 2;
+
+ @Protocol
+ private final int mProtocol;
+ @PortMatcher
+ private final int mPortMatcher;
+ private final int mPortNumber;
+ @Nullable
+ private final InetAddress mLocalAddress;
+
+ /**
+ * Describes a port.
+ *
+ * @param protocol The protocol of the port to match, {@link #PROTOCOL_TCP} or
+ * {@link #PROTOCOL_UDP}.
+ * @param portMatcher Whether to match the source port number of an incoming packet
+ * ({@link #MATCH_PORT_REMOTE}), or the destination port
+ * ({@link #MATCH_PORT_LOCAL}).
+ * @param portNumber The port number to match.
+ *
+ * @see #newLowPowerStandbyPortsLock(List)
+ */
+ public LowPowerStandbyPortDescription(@Protocol int protocol, @PortMatcher int portMatcher,
+ int portNumber) {
+ this.mProtocol = protocol;
+ this.mPortMatcher = portMatcher;
+ this.mPortNumber = portNumber;
+ this.mLocalAddress = null;
+ }
+
+ /**
+ * Describes a port.
+ *
+ * @param protocol The protocol of the port to match, {@link #PROTOCOL_TCP} or
+ * {@link #PROTOCOL_UDP}.
+ * @param portMatcher Whether to match the source port number of an incoming packet
+ * ({@link #MATCH_PORT_REMOTE}), or the destination port
+ * ({@link #MATCH_PORT_LOCAL}).
+ * @param portNumber The port number to match.
+ * @param localAddress The local address to match.
+ *
+ * @see #newLowPowerStandbyPortsLock(List)
+ */
+ public LowPowerStandbyPortDescription(@Protocol int protocol, @PortMatcher int portMatcher,
+ int portNumber, @Nullable InetAddress localAddress) {
+ this.mProtocol = protocol;
+ this.mPortMatcher = portMatcher;
+ this.mPortNumber = portNumber;
+ this.mLocalAddress = localAddress;
+ }
+
+ private String protocolToString(int protocol) {
+ switch (protocol) {
+ case PROTOCOL_TCP: return "TCP";
+ case PROTOCOL_UDP: return "UDP";
+ }
+ return String.valueOf(protocol);
+ }
+
+ private String portMatcherToString(int portMatcher) {
+ switch (portMatcher) {
+ case MATCH_PORT_LOCAL: return "MATCH_PORT_LOCAL";
+ case MATCH_PORT_REMOTE: return "MATCH_PORT_REMOTE";
+ }
+ return String.valueOf(portMatcher);
+ }
+
+ /**
+ * Returns the described port's protocol,
+ * either {@link #PROTOCOL_TCP} or {@link #PROTOCOL_UDP}.
+ *
+ * @see #PROTOCOL_TCP
+ * @see #PROTOCOL_UDP
+ * @see #getPortNumber()
+ * @see #getPortMatcher()
+ */
+ @Protocol
+ public int getProtocol() {
+ return mProtocol;
+ }
+
+ /**
+ * Returns how the port number ({@link #getPortNumber()}) should be matched against
+ * incoming packets.
+ * Either {@link #PROTOCOL_TCP} or {@link #PROTOCOL_UDP}.
+ *
+ * @see #PROTOCOL_TCP
+ * @see #PROTOCOL_UDP
+ * @see #getPortNumber()
+ * @see #getProtocol()
+ */
+ @PortMatcher
+ public int getPortMatcher() {
+ return mPortMatcher;
+ }
+
+ /**
+ * Returns how the port number that incoming packets should be matched against.
+ *
+ * @see #getPortMatcher()
+ * @see #getProtocol()
+ */
+ public int getPortNumber() {
+ return mPortNumber;
+ }
+
+ /**
+ * Returns the bind address to match against, or {@code null} if matching against any
+ * bind address.
+ *
+ * @see #getPortMatcher()
+ * @see #getProtocol()
+ */
+ @Nullable
+ public InetAddress getLocalAddress() {
+ return mLocalAddress;
+ }
+
+ @Override
+ public String toString() {
+ return "PortDescription{"
+ + "mProtocol=" + protocolToString(mProtocol)
+ + ", mPortMatcher=" + portMatcherToString(mPortMatcher)
+ + ", mPortNumber=" + mPortNumber
+ + ", mLocalAddress=" + mLocalAddress
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof LowPowerStandbyPortDescription)) return false;
+ LowPowerStandbyPortDescription that = (LowPowerStandbyPortDescription) o;
+ return mProtocol == that.mProtocol && mPortMatcher == that.mPortMatcher
+ && mPortNumber == that.mPortNumber && Objects.equals(mLocalAddress,
+ that.mLocalAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mProtocol, mPortMatcher, mPortNumber, mLocalAddress);
+ }
+
+ /** @hide */
+ public static IPowerManager.LowPowerStandbyPortDescription toParcelable(
+ LowPowerStandbyPortDescription portDescription) {
+ if (portDescription == null) {
+ return null;
+ }
+
+ IPowerManager.LowPowerStandbyPortDescription parcelablePortDescription =
+ new IPowerManager.LowPowerStandbyPortDescription();
+ parcelablePortDescription.protocol = portDescription.mProtocol;
+ parcelablePortDescription.portMatcher = portDescription.mPortMatcher;
+ parcelablePortDescription.portNumber = portDescription.mPortNumber;
+ if (portDescription.mLocalAddress != null) {
+ parcelablePortDescription.localAddress = portDescription.mLocalAddress.getAddress();
+ }
+ return parcelablePortDescription;
+ }
+
+ /** @hide */
+ public static List<IPowerManager.LowPowerStandbyPortDescription> toParcelable(
+ List<LowPowerStandbyPortDescription> portDescriptions) {
+ if (portDescriptions == null) {
+ return null;
+ }
+
+ ArrayList<IPowerManager.LowPowerStandbyPortDescription> result = new ArrayList<>();
+ for (LowPowerStandbyPortDescription port : portDescriptions) {
+ result.add(toParcelable(port));
+ }
+ return result;
+ }
+
+ /** @hide */
+ public static LowPowerStandbyPortDescription fromParcelable(
+ IPowerManager.LowPowerStandbyPortDescription parcelablePortDescription) {
+ if (parcelablePortDescription == null) {
+ return null;
+ }
+
+ InetAddress localAddress = null;
+ if (parcelablePortDescription.localAddress != null) {
+ try {
+ localAddress = InetAddress.getByAddress(parcelablePortDescription.localAddress);
+ } catch (UnknownHostException e) {
+ Log.w(TAG, "Address has invalid length", e);
+ }
+ }
+ return new LowPowerStandbyPortDescription(
+ parcelablePortDescription.protocol,
+ parcelablePortDescription.portMatcher,
+ parcelablePortDescription.portNumber,
+ localAddress);
+ }
+
+ /** @hide */
+ public static List<LowPowerStandbyPortDescription> fromParcelable(
+ List<IPowerManager.LowPowerStandbyPortDescription> portDescriptions) {
+ if (portDescriptions == null) {
+ return null;
+ }
+
+ ArrayList<LowPowerStandbyPortDescription> result = new ArrayList<>();
+ for (IPowerManager.LowPowerStandbyPortDescription port : portDescriptions) {
+ result.add(fromParcelable(port));
+ }
+ return result;
+ }
+ }
+
+ /**
+ * An object that can be used to request network ports to remain open during Low Power Standby.
+ *
+ * <p>Use {@link #newLowPowerStandbyPortsLock} to create a ports lock, and {@link #acquire()}
+ * to request the ports to remain open. The request is only respected if the app requesting the
+ * lock is exempt from Low Power Standby ({@link #isExemptFromLowPowerStandby()}).
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("NotCloseable")
+ public final class LowPowerStandbyPortsLock {
+ private final IBinder mToken;
+ private final List<LowPowerStandbyPortDescription> mPorts;
+ private boolean mHeld;
+
+ LowPowerStandbyPortsLock(List<LowPowerStandbyPortDescription> ports) {
+ mPorts = ports;
+ mToken = new Binder();
+ }
+
+ /** Request the ports to remain open during standby. */
+ @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+ public void acquire() {
+ synchronized (mToken) {
+ try {
+ mService.acquireLowPowerStandbyPorts(mToken,
+ LowPowerStandbyPortDescription.toParcelable(mPorts));
+ mHeld = true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Release the request, allowing these ports to be blocked during standby.
+ *
+ * <p>Note: This lock is not reference counted, so calling this method will release the lock
+ * regardless of how many times {@link #acquire()} has been called before.
+ */
+ @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+ public void release() {
+ synchronized (mToken) {
+ try {
+ mService.releaseLowPowerStandbyPorts(mToken);
+ mHeld = false;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ synchronized (mToken) {
+ if (mHeld) {
+ Log.wtf(TAG, "LowPowerStandbyPorts finalized while still held");
+ release();
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 listener interface to get notified when the wakelock is enabled/disabled.
+ */
+ public interface WakeLockStateListener {
+ /**
+ * Frameworks could disable the wakelock because either device's power allowlist has
+ * changed, or the app's wakelock has exceeded its quota, or the app goes into cached
+ * state.
+ * <p>
+ * This callback is called whenever the wakelock's state has changed.
+ * </p>
+ *
+ * @param enabled true is enabled, false is disabled.
+ */
+ void onStateChanged(boolean enabled);
+ }
+
+ /**
+ * 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 int mTagHash;
+ 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 int mDisplayId;
+ private WakeLockStateListener mListener;
+ private IWakeLockCallback mCallback;
+
+ private final Runnable mReleaser = () -> release(RELEASE_FLAG_TIMEOUT);
+
+ WakeLock(int flags, String tag, String packageName, int displayId) {
+ mFlags = flags;
+ mTag = tag;
+ mTagHash = mTag.hashCode();
+ mPackageName = packageName;
+ mToken = new Binder();
+ mDisplayId = displayId;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ synchronized (mToken) {
+ if (mHeld) {
+ Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+ "WakeLocks", mTagHash);
+ 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.asyncTraceForTrackBegin(Trace.TRACE_TAG_POWER,
+ "WakeLocks", mTag, mTagHash);
+ try {
+ mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
+ mHistoryTag, mDisplayId, mCallback);
+ } 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.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
+ "WakeLocks", mTagHash);
+ 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;
+ mTagHash = mTag.hashCode();
+ }
+
+ /** @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 dumpDebug(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.dumpDebug(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
+ */
+ @SuppressLint("WakelockTimeout")
+ public Runnable wrap(Runnable r) {
+ acquire();
+ return () -> {
+ try {
+ r.run();
+ } finally {
+ release();
+ }
+ };
+ }
+
+ /**
+ * Set the listener to get notified when the wakelock is enabled/disabled.
+ *
+ * @param executor {@link Executor} to handle listener callback.
+ * @param listener listener to be added, set the listener to null to cancel a listener.
+ */
+ public void setStateListener(@NonNull @CallbackExecutor Executor executor,
+ @Nullable WakeLockStateListener listener) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ synchronized (mToken) {
+ if (listener != mListener) {
+ mListener = listener;
+ if (listener != null) {
+ mCallback = new IWakeLockCallback.Stub() {
+ public void onStateChanged(boolean enabled) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ listener.onStateChanged(enabled);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ } else {
+ mCallback = null;
+ }
+ if (mHeld) {
+ try {
+ mService.updateWakeLockCallback(mToken, mCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static void invalidatePowerSaveModeCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY);
+ }
+
+ /**
+ * @hide
+ */
+ public static void invalidateIsInteractiveCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_INTERACTIVE_PROPERTY);
+ }
+}
diff --git a/android-34/android/os/PowerManagerInternal.java b/android-34/android/os/PowerManagerInternal.java
new file mode 100644
index 0000000..8afd6de
--- /dev/null
+++ b/android-34/android/os/PowerManagerInternal.java
@@ -0,0 +1,350 @@
+/*
+ * 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 android.view.KeyEvent;
+
+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 Float.NaN to disable the override.
+ */
+ public abstract void setScreenBrightnessOverrideFromWindowManager(float 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);
+
+ /**
+ * Updates the Low Power Standby allowlist.
+ *
+ * @param uids UIDs that are exempt from Low Power Standby restrictions
+ */
+ public abstract void setLowPowerStandbyAllowlist(int[] uids);
+
+ /**
+ * Used by LowPowerStandbyController to notify the power manager that Low Power Standby's
+ * active state has changed.
+ *
+ * @param active {@code true} to activate Low Power Standby, {@code false} to turn it off.
+ */
+ public abstract void setLowPowerStandbyActive(boolean active);
+
+ 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);
+
+ /**
+ * Boost: It is sent when user interacting with the device, for example,
+ * touchscreen events are incoming.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Boost.aidl
+ */
+ public static final int BOOST_INTERACTION = 0;
+
+ /**
+ * Boost: It indicates that the framework is likely to provide a new display
+ * frame soon. This implies that the device should ensure that the display
+ * processing path is powered up and ready to receive that update.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Boost.aidl
+ */
+ public static final int BOOST_DISPLAY_UPDATE_IMMINENT = 1;
+
+ /**
+ * SetPowerBoost() indicates the device may need to boost some resources, as
+ * the load is likely to increase before the kernel governors can react.
+ * Depending on the boost, it may be appropriate to raise the frequencies of
+ * CPU, GPU, memory subsystem, or stop CPU from going into deep sleep state.
+ *
+ * @param boost Boost which is to be set with a timeout.
+ * @param durationMs The expected duration of the user's interaction, if
+ * known, or 0 if the expected duration is unknown.
+ * a negative value indicates canceling previous boost.
+ * A given platform can choose to boost some time based on durationMs,
+ * and may also pick an appropriate timeout for 0 case.
+ */
+ public abstract void setPowerBoost(int boost, int durationMs);
+
+ /**
+ * Mode: It indicates that the device is to allow wake up when the screen
+ * is tapped twice.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_DOUBLE_TAP_TO_WAKE = 0;
+
+ /**
+ * Mode: It indicates Low power mode is activated or not. Low power mode
+ * is intended to save battery at the cost of performance.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_LOW_POWER = 1;
+
+ /**
+ * Mode: It indicates Sustained Performance mode is activated or not.
+ * Sustained performance mode is intended to provide a consistent level of
+ * performance for a prolonged amount of time.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_SUSTAINED_PERFORMANCE = 2;
+
+ /**
+ * Mode: It sets the device to a fixed performance level which can be sustained
+ * under normal indoor conditions for at least 10 minutes.
+ * Fixed performance mode puts both upper and lower bounds on performance such
+ * that any workload run while in a fixed performance mode should complete in
+ * a repeatable amount of time.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_FIXED_PERFORMANCE = 3;
+
+ /**
+ * Mode: It indicates VR Mode is activated or not. VR mode is intended to
+ * provide minimum guarantee for performance for the amount of time the device
+ * can sustain it.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_VR = 4;
+
+ /**
+ * Mode: It indicates that an application has been launched.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_LAUNCH = 5;
+
+ /**
+ * Mode: It indicates that the device is about to enter a period of expensive
+ * rendering.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_EXPENSIVE_RENDERING = 6;
+
+ /**
+ * Mode: It indicates that the device is about entering/leaving interactive
+ * state or on-interactive state.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_INTERACTIVE = 7;
+
+ /**
+ * Mode: It indicates the device is in device idle, externally known as doze.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_DEVICE_IDLE = 8;
+
+ /**
+ * Mode: It indicates that display is either off or still on but is optimized
+ * for low power.
+ * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+ */
+ public static final int MODE_DISPLAY_INACTIVE = 9;
+
+ /**
+ * SetPowerMode() is called to enable/disable specific hint mode, which
+ * may result in adjustment of power/performance parameters of the
+ * cpufreq governor and other controls on device side.
+ *
+ * @param mode Mode which is to be enable/disable.
+ * @param enabled true to enable, false to disable the mode.
+ */
+ public abstract void setPowerMode(int mode, boolean enabled);
+
+ /** 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();
+
+ /** Returns information about the last event to go to sleep. */
+ public abstract PowerManager.SleepData getLastGoToSleep();
+
+ /** Allows power button to intercept a power key button press. */
+ public abstract boolean interceptPowerKeyDown(KeyEvent event);
+
+ /**
+ * Internal version of {@link android.os.PowerManager#nap} which allows for napping while the
+ * device is not awake.
+ */
+ public abstract void nap(long eventTime, boolean allowWake);
+
+ /**
+ * Returns true if ambient display is suppressed by any app with any token. This method will
+ * return false if ambient display is not available.
+ */
+ public abstract boolean isAmbientDisplaySuppressed();
+}
diff --git a/android-34/android/os/PowerSaveState.java b/android-34/android/os/PowerSaveState.java
new file mode 100644
index 0000000..7d3cd06
--- /dev/null
+++ b/android-34/android/os/PowerSaveState.java
@@ -0,0 +1,124 @@
+/* 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 int soundTriggerMode;
+ public final float brightnessFactor;
+
+ public PowerSaveState(Builder builder) {
+ batterySaverEnabled = builder.mBatterySaverEnabled;
+ locationMode = builder.mLocationMode;
+ soundTriggerMode = builder.mSoundTriggerMode;
+ brightnessFactor = builder.mBrightnessFactor;
+ globalBatterySaverEnabled = builder.mGlobalBatterySaverEnabled;
+ }
+
+ public PowerSaveState(Parcel in) {
+ batterySaverEnabled = in.readByte() != 0;
+ globalBatterySaverEnabled = in.readByte() != 0;
+ locationMode = in.readInt();
+ soundTriggerMode = 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.writeInt(soundTriggerMode);
+ dest.writeFloat(brightnessFactor);
+ }
+
+ public static final class Builder {
+ private boolean mBatterySaverEnabled = false;
+ private boolean mGlobalBatterySaverEnabled = false;
+ private int mLocationMode = 0;
+ private int mSoundTriggerMode = PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+ 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 setSoundTriggerMode(int mode) {
+ mSoundTriggerMode = mode;
+ 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-34/android/os/PowerWhitelistManager.java b/android-34/android/os/PowerWhitelistManager.java
new file mode 100644
index 0000000..4ce31e9
--- /dev/null
+++ b/android-34/android/os/PowerWhitelistManager.java
@@ -0,0 +1,544 @@
+/*
+ * 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.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Interface to access and modify the permanent and temporary power save whitelist. The two lists
+ * are kept separately. Apps placed on the permanent whitelist are only removed via an explicit
+ * removeFromWhitelist call. Apps whitelisted by default by the system cannot be removed. Apps
+ * placed on the temporary whitelist are removed from that whitelist after a predetermined amount of
+ * time.
+ *
+ * @deprecated Use {@link PowerExemptionManager} instead
+ * @hide
+ */
+@SystemApi
+@Deprecated
+@SystemService(Context.POWER_WHITELIST_MANAGER)
+public class PowerWhitelistManager {
+ private final Context mContext;
+ // Proxy to DeviceIdleController for now
+ // TODO: migrate to PowerWhitelistController
+ private final IDeviceIdleController mService;
+
+ private final PowerExemptionManager mPowerExemptionManager;
+
+ /**
+ * Indicates that an unforeseen event has occurred and the app should be whitelisted to handle
+ * it.
+ */
+ public static final int EVENT_UNSPECIFIED = PowerExemptionManager.EVENT_UNSPECIFIED;
+
+ /**
+ * Indicates that an SMS event has occurred and the app should be whitelisted to handle it.
+ */
+ public static final int EVENT_SMS = PowerExemptionManager.EVENT_SMS;
+
+ /**
+ * Indicates that an MMS event has occurred and the app should be whitelisted to handle it.
+ */
+ public static final int EVENT_MMS = PowerExemptionManager.EVENT_MMS;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EVENT_"}, value = {
+ EVENT_UNSPECIFIED,
+ EVENT_SMS,
+ EVENT_MMS,
+ })
+ public @interface WhitelistEvent {
+ }
+
+ /**
+ * Allow the temp allowlist behavior, plus allow foreground service start from background.
+ */
+ public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED =
+ PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+ /**
+ * Only allow the temp allowlist behavior, not allow foreground service start from
+ * background.
+ */
+ public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED =
+ PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
+
+ /**
+ * The list of temp allowlist types.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "TEMPORARY_ALLOWLIST_TYPE_" }, value = {
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TempAllowListType {}
+
+ /* Reason code for BG-FGS-launch. */
+ /**
+ * BG-FGS-launch is denied.
+ * @hide
+ */
+ public static final int REASON_DENIED = PowerExemptionManager.REASON_DENIED;
+
+ /* Reason code range 0-9 are reserved for default reasons */
+ /**
+ * The default reason code if reason is unknown.
+ */
+ public static final int REASON_UNKNOWN = PowerExemptionManager.REASON_UNKNOWN;
+ /**
+ * Use REASON_OTHER if there is no better choice.
+ */
+ public static final int REASON_OTHER = PowerExemptionManager.REASON_OTHER;
+
+ /* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */
+ /** @hide */
+ public static final int REASON_PROC_STATE_PERSISTENT =
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
+ /** @hide */
+ public static final int REASON_PROC_STATE_PERSISTENT_UI =
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
+ /** @hide */
+ public static final int REASON_PROC_STATE_TOP = PowerExemptionManager.REASON_PROC_STATE_TOP;
+ /** @hide */
+ public static final int REASON_PROC_STATE_BTOP = PowerExemptionManager.REASON_PROC_STATE_BTOP;
+ /** @hide */
+ public static final int REASON_PROC_STATE_FGS = PowerExemptionManager.REASON_PROC_STATE_FGS;
+ /** @hide */
+ public static final int REASON_PROC_STATE_BFGS = PowerExemptionManager.REASON_PROC_STATE_BFGS;
+
+ /* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */
+ /** @hide */
+ public static final int REASON_UID_VISIBLE = PowerExemptionManager.REASON_UID_VISIBLE;
+ /** @hide */
+ public static final int REASON_SYSTEM_UID = PowerExemptionManager.REASON_SYSTEM_UID;
+ /** @hide */
+ public static final int REASON_ACTIVITY_STARTER = PowerExemptionManager.REASON_ACTIVITY_STARTER;
+ /** @hide */
+ public static final int REASON_START_ACTIVITY_FLAG =
+ PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
+ /** @hide */
+ public static final int REASON_FGS_BINDING = PowerExemptionManager.REASON_FGS_BINDING;
+ /** @hide */
+ public static final int REASON_DEVICE_OWNER = PowerExemptionManager.REASON_DEVICE_OWNER;
+ /** @hide */
+ public static final int REASON_PROFILE_OWNER = PowerExemptionManager.REASON_PROFILE_OWNER;
+ /** @hide */
+ public static final int REASON_COMPANION_DEVICE_MANAGER =
+ PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
+ /**
+ * START_ACTIVITIES_FROM_BACKGROUND permission.
+ * @hide
+ */
+ public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION =
+ PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+ /**
+ * START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
+ * @hide
+ */
+ public static final int REASON_BACKGROUND_FGS_PERMISSION =
+ PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
+ /** @hide */
+ public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION =
+ PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+ /** @hide */
+ public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION =
+ PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION;
+ /** @hide */
+ public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION =
+ PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ /** @hide */
+ public static final int REASON_DEVICE_DEMO_MODE = PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
+ /** @hide */
+ public static final int REASON_ALLOWLISTED_PACKAGE =
+ PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+ /** @hide */
+ public static final int REASON_APPOP = PowerExemptionManager.REASON_APPOP;
+
+ /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist.
+ Reason code for temp and system allowlist starts here.
+ Reason code range 100-199 are reserved for public reasons. */
+ /**
+ * Set temp-allowlist for location geofence purpose.
+ */
+ public static final int REASON_GEOFENCING = PowerExemptionManager.REASON_GEOFENCING;
+ /**
+ * Set temp-allowlist for server push messaging.
+ */
+ public static final int REASON_PUSH_MESSAGING = PowerExemptionManager.REASON_PUSH_MESSAGING;
+ /**
+ * Set temp-allowlist for server push messaging over the quota.
+ */
+ public static final int REASON_PUSH_MESSAGING_OVER_QUOTA =
+ PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+ /**
+ * Set temp-allowlist for activity recognition.
+ */
+ public static final int REASON_ACTIVITY_RECOGNITION =
+ PowerExemptionManager.REASON_ACTIVITY_RECOGNITION;
+
+ /* Reason code range 200-299 are reserved for broadcast actions */
+ /**
+ * Broadcast ACTION_BOOT_COMPLETED.
+ * @hide
+ */
+ public static final int REASON_BOOT_COMPLETED = PowerExemptionManager.REASON_BOOT_COMPLETED;
+ /**
+ * Broadcast ACTION_PRE_BOOT_COMPLETED.
+ * @hide
+ */
+ public static final int REASON_PRE_BOOT_COMPLETED =
+ PowerExemptionManager.REASON_PRE_BOOT_COMPLETED;
+ /**
+ * Broadcast ACTION_LOCKED_BOOT_COMPLETED.
+ * @hide
+ */
+ public static final int REASON_LOCKED_BOOT_COMPLETED =
+ PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
+
+ /* Reason code range 300-399 are reserved for other internal reasons */
+ /**
+ * Device idle system allowlist, including EXCEPT-IDLE
+ * @hide
+ */
+ public static final int REASON_SYSTEM_ALLOW_LISTED =
+ PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+ /** @hide */
+ public static final int REASON_ALARM_MANAGER_ALARM_CLOCK =
+ PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
+ /**
+ * AlarmManagerService.
+ * @hide
+ */
+ public static final int REASON_ALARM_MANAGER_WHILE_IDLE =
+ PowerExemptionManager.REASON_ALARM_MANAGER_WHILE_IDLE;
+ /**
+ * ActiveServices.
+ * @hide
+ */
+ public static final int REASON_SERVICE_LAUNCH = PowerExemptionManager.REASON_SERVICE_LAUNCH;
+ /**
+ * KeyChainSystemService.
+ * @hide
+ */
+ public static final int REASON_KEY_CHAIN = PowerExemptionManager.REASON_KEY_CHAIN;
+ /**
+ * PackageManagerService.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_VERIFIER = PowerExemptionManager.REASON_PACKAGE_VERIFIER;
+ /**
+ * SyncManager.
+ * @hide
+ */
+ public static final int REASON_SYNC_MANAGER = PowerExemptionManager.REASON_SYNC_MANAGER;
+ /**
+ * DomainVerificationProxyV1.
+ * @hide
+ */
+ public static final int REASON_DOMAIN_VERIFICATION_V1 =
+ PowerExemptionManager.REASON_DOMAIN_VERIFICATION_V1;
+ /**
+ * DomainVerificationProxyV2.
+ * @hide
+ */
+ public static final int REASON_DOMAIN_VERIFICATION_V2 =
+ PowerExemptionManager.REASON_DOMAIN_VERIFICATION_V2;
+ /** @hide */
+ public static final int REASON_VPN = 309;
+ /**
+ * NotificationManagerService.
+ * @hide
+ */
+ public static final int REASON_NOTIFICATION_SERVICE =
+ PowerExemptionManager.REASON_NOTIFICATION_SERVICE;
+ /**
+ * Broadcast ACTION_MY_PACKAGE_REPLACED.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_REPLACED = PowerExemptionManager.REASON_PACKAGE_REPLACED;
+ /**
+ * LocationProvider.
+ * @hide
+ */
+ @SystemApi
+ public static final int REASON_LOCATION_PROVIDER =
+ PowerExemptionManager.REASON_LOCATION_PROVIDER;
+ /**
+ * MediaButtonReceiver.
+ * @hide
+ */
+ public static final int REASON_MEDIA_BUTTON = PowerExemptionManager.REASON_MEDIA_BUTTON;
+ /**
+ * InboundSmsHandler.
+ * @hide
+ */
+ public static final int REASON_EVENT_SMS = PowerExemptionManager.REASON_EVENT_SMS;
+ /**
+ * InboundSmsHandler.
+ * @hide
+ */
+ public static final int REASON_EVENT_MMS = PowerExemptionManager.REASON_EVENT_MMS;
+ /**
+ * Shell app.
+ * @hide
+ */
+ public static final int REASON_SHELL = PowerExemptionManager.REASON_SHELL;
+
+ /**
+ * The list of BG-FGS-Launch and temp-allowlist reason code.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "REASON_" }, value = {
+ // BG-FGS-Launch reasons.
+ REASON_DENIED,
+ REASON_UNKNOWN,
+ REASON_OTHER,
+ REASON_PROC_STATE_PERSISTENT,
+ REASON_PROC_STATE_PERSISTENT_UI,
+ REASON_PROC_STATE_TOP,
+ REASON_PROC_STATE_BTOP,
+ REASON_PROC_STATE_FGS,
+ REASON_PROC_STATE_BFGS,
+ REASON_UID_VISIBLE,
+ REASON_SYSTEM_UID,
+ REASON_ACTIVITY_STARTER,
+ REASON_START_ACTIVITY_FLAG,
+ REASON_FGS_BINDING,
+ REASON_DEVICE_OWNER,
+ REASON_PROFILE_OWNER,
+ REASON_COMPANION_DEVICE_MANAGER,
+ REASON_BACKGROUND_ACTIVITY_PERMISSION,
+ REASON_BACKGROUND_FGS_PERMISSION,
+ REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION,
+ REASON_INSTR_BACKGROUND_FGS_PERMISSION,
+ REASON_SYSTEM_ALERT_WINDOW_PERMISSION,
+ REASON_DEVICE_DEMO_MODE,
+ REASON_ALLOWLISTED_PACKAGE,
+ REASON_APPOP,
+ // temp and system allowlist reasons.
+ REASON_GEOFENCING,
+ REASON_PUSH_MESSAGING,
+ REASON_PUSH_MESSAGING_OVER_QUOTA,
+ REASON_ACTIVITY_RECOGNITION,
+ REASON_BOOT_COMPLETED,
+ REASON_PRE_BOOT_COMPLETED,
+ REASON_LOCKED_BOOT_COMPLETED,
+ REASON_SYSTEM_ALLOW_LISTED,
+ REASON_ALARM_MANAGER_ALARM_CLOCK,
+ REASON_ALARM_MANAGER_WHILE_IDLE,
+ REASON_SERVICE_LAUNCH,
+ REASON_KEY_CHAIN,
+ REASON_PACKAGE_VERIFIER,
+ REASON_SYNC_MANAGER,
+ REASON_DOMAIN_VERIFICATION_V1,
+ REASON_DOMAIN_VERIFICATION_V2,
+ REASON_VPN,
+ REASON_NOTIFICATION_SERVICE,
+ REASON_PACKAGE_REPLACED,
+ REASON_LOCATION_PROVIDER,
+ REASON_MEDIA_BUTTON,
+ REASON_EVENT_SMS,
+ REASON_EVENT_MMS,
+ REASON_SHELL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReasonCode {}
+
+ /**
+ * @hide
+ */
+ public PowerWhitelistManager(@NonNull Context context) {
+ mContext = context;
+ mService = context.getSystemService(DeviceIdleManager.class).getService();
+ mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
+ }
+
+ /**
+ * Add the specified package to the permanent power save whitelist.
+ *
+ * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(String)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void addToWhitelist(@NonNull String packageName) {
+ mPowerExemptionManager.addToPermanentAllowList(packageName);
+ }
+
+ /**
+ * Add the specified packages to the permanent power save whitelist.
+ *
+ * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(List)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void addToWhitelist(@NonNull List<String> packageNames) {
+ mPowerExemptionManager.addToPermanentAllowList(packageNames);
+ }
+
+ /**
+ * Get a list of app IDs of app that are whitelisted. This does not include temporarily
+ * whitelisted apps.
+ *
+ * @param includingIdle Set to true if the app should be whitelisted from device idle as well
+ * as other power save restrictions
+ * @deprecated Use {@link PowerExemptionManager#getAllowListedAppIds(boolean)} instead
+ * @hide
+ */
+ @Deprecated
+ @NonNull
+ public int[] getWhitelistedAppIds(boolean includingIdle) {
+ return mPowerExemptionManager.getAllowListedAppIds(includingIdle);
+ }
+
+ /**
+ * Returns true if the app is whitelisted from power save restrictions. This does not include
+ * temporarily whitelisted apps.
+ *
+ * @param includingIdle Set to true if the app should be whitelisted from device
+ * idle as well as other power save restrictions
+ * @deprecated Use {@link PowerExemptionManager#isAllowListed(String, boolean)} instead
+ * @hide
+ */
+ @Deprecated
+ public boolean isWhitelisted(@NonNull String packageName, boolean includingIdle) {
+ return mPowerExemptionManager.isAllowListed(packageName, includingIdle);
+ }
+
+ /**
+ * Remove an app from the permanent power save whitelist. Only apps that were added via
+ * {@link #addToWhitelist(String)} or {@link #addToWhitelist(List)} will be removed. Apps
+ * whitelisted by default by the system cannot be removed.
+ *
+ * @param packageName The app to remove from the whitelist
+ * @deprecated Use {@link PowerExemptionManager#removeFromPermanentAllowList(String)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public void removeFromWhitelist(@NonNull String packageName) {
+ mPowerExemptionManager.removeFromPermanentAllowList(packageName);
+ }
+
+ /**
+ * Add an app to the temporary whitelist for a short amount of time.
+ *
+ * @param packageName The package to add to the temp whitelist
+ * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds)
+ * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
+ * @param reason a optional human readable reason string, could be null or empty string.
+ * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
+ * String, int, String, long)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
+ public void whitelistAppTemporarily(@NonNull String packageName, long durationMs,
+ @ReasonCode int reasonCode, @Nullable String reason) {
+ mPowerExemptionManager.addToTemporaryAllowList(packageName, reasonCode, reason, durationMs);
+ }
+
+ /**
+ * Add an app to the temporary whitelist for a short amount of time.
+ *
+ * @param packageName The package to add to the temp whitelist
+ * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds)
+ * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
+ * String, int, String, long)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
+ public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) {
+ mPowerExemptionManager.addToTemporaryAllowList(
+ packageName, REASON_UNKNOWN, packageName, durationMs);
+ }
+
+ /**
+ * Add an app to the temporary whitelist for a short amount of time for a specific reason. The
+ * temporary whitelist is kept separately from the permanent whitelist and apps are
+ * automatically removed from the temporary whitelist after a predetermined amount of time.
+ *
+ * @param packageName The package to add to the temp whitelist
+ * @param event The reason to add the app to the temp whitelist
+ * @param reason A human-readable reason explaining why the app is temp whitelisted. Only
+ * used for logging purposes. Could be null or empty string.
+ * @return The duration (in milliseconds) that the app is whitelisted for
+ * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
+ * String, int, String, int)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
+ public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
+ @WhitelistEvent int event, @Nullable String reason) {
+ return mPowerExemptionManager.addToTemporaryAllowListForEvent(
+ packageName, REASON_UNKNOWN, reason, event);
+ }
+
+ /**
+ * Add an app to the temporary whitelist for a short amount of time for a specific reason. The
+ * temporary whitelist is kept separately from the permanent whitelist and apps are
+ * automatically removed from the temporary whitelist after a predetermined amount of time.
+ *
+ * @param packageName The package to add to the temp whitelist
+ * @param event The reason to add the app to the temp whitelist
+ * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
+ * @param reason A human-readable reason explaining why the app is temp whitelisted. Only
+ * used for logging purposes. Could be null or empty string.
+ * @return The duration (in milliseconds) that the app is whitelisted for
+ * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
+ * String, int, String, int)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
+ public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
+ @WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
+ return mPowerExemptionManager.addToTemporaryAllowListForEvent(
+ packageName, reasonCode, reason, event);
+ }
+
+ /**
+ * @hide
+ *
+ * @deprecated Use {@link PowerExemptionManager#getReasonCodeFromProcState(int)} instead
+ */
+ @Deprecated
+ public static @ReasonCode int getReasonCodeFromProcState(int procState) {
+ return PowerExemptionManager.getReasonCodeFromProcState(procState);
+ }
+
+ /**
+ * Return string name of the integer reason code.
+ * @hide
+ * @param reasonCode
+ * @return string name of the reason code.
+ * @deprecated Use {@link PowerExemptionManager#reasonCodeToString(int)} instead
+ */
+ @Deprecated
+ public static String reasonCodeToString(@ReasonCode int reasonCode) {
+ return PowerExemptionManager.reasonCodeToString(reasonCode);
+ }
+}
diff --git a/android-34/android/os/Process.java b/android-34/android/os/Process.java
new file mode 100644
index 0000000..04525e8
--- /dev/null
+++ b/android-34/android/os/Process.java
@@ -0,0 +1,1673 @@
+/*
+ * 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.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UptimeMillisLong;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build.VERSION_CODES;
+import android.sysprop.MemoryProperties;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructPollfd;
+import android.util.Pair;
+import android.webkit.WebViewZygote;
+
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int LOG_UID = 1007;
+
+ /**
+ * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd,
+ * vendor HAL, etc.
+ */
+ public static final int WIFI_UID = 1010;
+
+ /**
+ * Defines the UID/GID for the mediaserver process.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int MEDIA_UID = 1013;
+
+ /**
+ * Defines the UID/GID for the DRM process.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int DRM_UID = 1019;
+
+ /**
+ * Defines the GID for the group that allows write access to the internal media storage.
+ * @hide
+ */
+ public static final int SDCARD_RW_GID = 1015;
+
+ /**
+ * Defines the UID/GID for the group that controls VPN services.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SystemApi(client = MODULE_LIBRARIES)
+ 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 credstore.
+ * @hide
+ */
+ public static final int CREDSTORE_UID = 1076;
+
+ /**
+ * Defines the UID/GID for the NFC service process.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ @SystemApi(client = MODULE_LIBRARIES)
+ 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 statsd
+ * @hide
+ */
+ public static final int STATSD_UID = 1066;
+
+ /**
+ * 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;
+
+ /**
+ * Defines the UID/GID for fs-verity certificate ownership in keystore.
+ * @hide
+ */
+ public static final int FSVERITY_CERT_UID = 1075;
+
+ /**
+ * GID that gives access to USB OTG (unreliable) volumes on /mnt/media_rw/<vol name>
+ * @hide
+ */
+ public static final int EXTERNAL_STORAGE_GID = 1077;
+
+ /**
+ * GID that gives write access to app-private data directories on external
+ * storage (used on devices without sdcardfs only).
+ * @hide
+ */
+ public static final int EXT_DATA_RW_GID = 1078;
+
+ /**
+ * GID that gives write access to app-private OBB directories on external
+ * storage (used on devices without sdcardfs only).
+ * @hide
+ */
+ public static final int EXT_OBB_RW_GID = 1079;
+
+ /**
+ * Defines the UID/GID for the Uwb service process.
+ * @hide
+ */
+ public static final int UWB_UID = 1083;
+
+ /**
+ * Defines a virtual UID that is used to aggregate data related to SDK sandbox UIDs.
+ * {@see SdkSandboxManager}
+ * @hide
+ */
+ @TestApi
+ public static final int SDK_SANDBOX_VIRTUAL_UID = 1090;
+
+ /**
+ * GID that corresponds to the INTERNET permission.
+ * Must match the value of AID_INET.
+ * @hide
+ */
+ public static final int INET_GID = 3003;
+
+ /** {@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;
+
+ /**
+ * Defines the start of a range of UIDs going from this number to
+ * {@link #LAST_SDK_SANDBOX_UID} that are reserved for assigning to
+ * sdk sandbox processes. There is a 1-1 mapping between a sdk sandbox
+ * process UID and the app that it belongs to, which can be computed by
+ * subtracting (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID) from the
+ * uid of a sdk sandbox process.
+ *
+ * Note that there are no GIDs associated with these processes; storage
+ * attribution for them will be done using project IDs.
+ * @hide
+ */
+ public static final int FIRST_SDK_SANDBOX_UID = 20000;
+
+ /**
+ * Last UID that is used for sdk sandbox processes.
+ * @hide
+ */
+ public static final int LAST_SDK_SANDBOX_UID = 29999;
+
+ /**
+ * 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
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final int FIRST_ISOLATED_UID = 99000;
+
+ /**
+ * Last uid used for fully isolated sandboxed processes (with no permissions of their own)
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @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;
+
+ /**
+ * An invalid PID value.
+ */
+ public static final int INVALID_PID = -1;
+
+ /**
+ * 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;
+
+ /**
+ * Priority we boost main thread and RT of top app to.
+ * @hide
+ */
+ public static final int THREAD_PRIORITY_TOP_APP_BOOST = -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.
+ * @hide
+ */
+ public static final int THREAD_GROUP_BACKGROUND = 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;
+
+ /**
+ * When the process started and ActivityThread.handleBindApplication() was executed.
+ */
+ private static long sStartElapsedRealtime;
+
+ /**
+ * When the process started and ActivityThread.handleBindApplication() was executed.
+ */
+ private static long sStartUptimeMillis;
+
+ /**
+ * When the activity manager was about to ask zygote to fork.
+ */
+ private static long sStartRequestedElapsedRealtime;
+
+ /**
+ * When the activity manager was about to ask zygote to fork.
+ */
+ private static long sStartRequestedUptimeMillis;
+
+ private static final int PIDFD_UNKNOWN = 0;
+ private static final int PIDFD_SUPPORTED = 1;
+ private static final int PIDFD_UNSUPPORTED = 2;
+
+ /**
+ * Whether or not the underlying OS supports pidfd
+ */
+ private static int sPidFdSupported = PIDFD_UNKNOWN;
+
+ /**
+ * Value used to indicate that there is no special information about an application launch. App
+ * launches with this policy will occur through the primary or secondary Zygote with no special
+ * treatment.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_EMPTY = 0;
+
+ /**
+ * Flag used to indicate that an application launch is user-visible and latency sensitive. Any
+ * launch with this policy will use a Unspecialized App Process Pool if the target Zygote
+ * supports it.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE = 1 << 0;
+
+ /**
+ * Flag used to indicate that the launch is one in a series of app launches that will be
+ * performed in quick succession. For future use.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_BATCH_LAUNCH = 1 << 1;
+
+ /**
+ * Flag used to indicate that the current launch event is for a system process. All system
+ * processes are equally important, so none of them should be prioritized over the others.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS = 1 << 2;
+
+ /**
+ * State associated with the zygote process.
+ * @hide
+ */
+ public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
+
+
+ /**
+ * The process name set via {@link #setArgV0(String)}.
+ */
+ private static String sArgV0;
+
+ /**
+ * 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 zygotePolicyFlags Flags used to determine how to launch the application
+ * @param isTopApp whether the process starts for high priority application.
+ * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+ * started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
+ * @param whitelistedDataInfoMap Map from allowlisted package names to private data directory
+ * volume UUID and inode number.
+ * @param bindMountAppsData whether zygote needs to mount CE and DE data.
+ * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
+ * @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,
+ int zygotePolicyFlags,
+ boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ whitelistedDataInfoMap,
+ boolean bindMountAppsData,
+ boolean bindMountAppStorageDirs,
+ @Nullable String[] zygoteArgs) {
+ return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
+ runtimeFlags, mountExternal, targetSdkVersion, seInfo,
+ abi, instructionSet, appDataDir, invokeWith, packageName,
+ zygotePolicyFlags, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+ bindMountAppStorageDirs, 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 long[] disabledCompatChanges,
+ @Nullable String[] zygoteArgs) {
+ // Webview zygote can't access app private data files, so doesn't need to know its data
+ // info.
+ return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
+ runtimeFlags, mountExternal, targetSdkVersion, seInfo,
+ abi, instructionSet, appDataDir, invokeWith, packageName,
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
+ disabledCompatChanges, /* pkgDataInfoMap */ null,
+ /* whitelistedDataInfoMap */ null, false, 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,
+ * but before any of the application code was executed.
+ */
+ @ElapsedRealtimeLong
+ public static long getStartElapsedRealtime() {
+ return sStartElapsedRealtime;
+ }
+
+ /**
+ * Return the {@link SystemClock#uptimeMillis()} at which this process was started,
+ * but before any of the application code was executed.
+ */
+ @UptimeMillisLong
+ public static long getStartUptimeMillis() {
+ return sStartUptimeMillis;
+ }
+
+ /**
+ * Return the {@link SystemClock#elapsedRealtime()} at which the system was about to
+ * start this process. i.e. before a zygote fork.
+ *
+ * <p>More precisely, the system may start app processes before there's a start request,
+ * in order to reduce the process start up latency, in which case this is set when the system
+ * decides to "specialize" the process into a requested app.
+ */
+ @ElapsedRealtimeLong
+ public static long getStartRequestedElapsedRealtime() {
+ return sStartRequestedElapsedRealtime;
+ }
+
+ /**
+ * Return the {@link SystemClock#uptimeMillis()} at which the system was about to
+ * start this process. i.e. before a zygote fork.
+ *
+ * <p>More precisely, the system may start app processes before there's a start request,
+ * in order to reduce the process start up latency, in which case this is set when the system
+ * decides to "specialize" the process into a requested app.
+ */
+ @UptimeMillisLong
+ public static long getStartRequestedUptimeMillis() {
+ return sStartRequestedUptimeMillis;
+ }
+
+ /** @hide */
+ public static final void setStartTimes(long elapsedRealtime, long uptimeMillis,
+ long startRequestedElapsedRealtime, long startRequestedUptime) {
+ sStartElapsedRealtime = elapsedRealtime;
+ sStartUptimeMillis = uptimeMillis;
+ sStartRequestedElapsedRealtime = startRequestedElapsedRealtime;
+ sStartRequestedUptimeMillis = startRequestedUptime;
+ }
+
+ /**
+ * 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(trackingBug = 171962076)
+ 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());
+ }
+
+ /**
+ * @deprecated Use {@link #isIsolatedUid(int)} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
+ public static final boolean isIsolated(int uid) {
+ return isIsolatedUid(uid);
+ }
+
+ /**
+ * Returns whether the process with the given {@code uid} is an isolated sandbox.
+ */
+ public static final boolean isIsolatedUid(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 whether the provided UID belongs to a SDK sandbox process.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @TestApi
+ public static final boolean isSdkSandboxUid(int uid) {
+ uid = UserHandle.getAppId(uid);
+ return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
+ }
+
+ /**
+ *
+ * Returns the app process corresponding to an sdk sandbox process.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @TestApi
+ public static final int getAppUidForSdkSandboxUid(int uid) {
+ return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
+ }
+
+ /**
+ *
+ * Returns the sdk sandbox process corresponding to an app process.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @TestApi
+ public static final int toSdkSandboxUid(int uid) {
+ return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
+ }
+
+ /**
+ * Returns whether the current process is a sdk sandbox process.
+ */
+ public static final boolean isSdkSandbox() {
+ return isSdkSandboxUid(myUid());
+ }
+
+ /**
+ * 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_BACKGROUND 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;
+
+ /**
+ * Freeze or unfreeze the specified process.
+ *
+ * @param pid Identifier of the process to freeze or unfreeze.
+ * @param uid Identifier of the user the process is running under.
+ * @param frozen Specify whether to free (true) or unfreeze (false).
+ *
+ * @hide
+ */
+ public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
+
+ /**
+ * Enable or disable the freezer. When enable == false all frozen processes are unfrozen,
+ * but aren't removed from the freezer. While in this state, processes can be added or removed
+ * by using setProcessFrozen, but they won't actually be frozen until the freezer is enabled
+ * again. If enable == true the freezer is enabled again, and all processes
+ * in the freezer (including the ones added while the freezer was disabled) are frozen.
+ *
+ * @param enable Specify whether to enable (true) or disable (false) the freezer.
+ *
+ * @hide
+ */
+ public static final native void enableFreezer(boolean enable);
+
+ /**
+ * Return the scheduling group of requested process.
+ *
+ * @hide
+ */
+ public static final native int getProcessGroup(int pid)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
+ *
+ * Create a new process group in the cgroup uid/pid hierarchy
+ *
+ * @return <0 in case of error
+ *
+ * @hide
+ */
+ public static final native int createProcessGroup(int uid, int pid);
+
+ /**
+ * 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(maxTargetSdk = VERSION_CODES.S, publicAlternatives = "Do not try to "
+ + "change the process name. (If you must, you could use {@code pthread_setname_np(3)}, "
+ + "but this could confuse the system)")
+ public static void setArgV0(@NonNull String text) {
+ sArgV0 = text;
+ setArgV0Native(text);
+ }
+
+ private static native void setArgV0Native(String text);
+
+ /**
+ * Return the name of this process. By default, the process name is the same as the app's
+ * package name, but this can be changed using {@code android:process}.
+ */
+ @NonNull
+ public static String myProcessName() {
+ // Note this could be different from the actual process name if someone changes the
+ // process name using native code (using pthread_setname_np()). But sArgV0
+ // is the name that the system thinks this process has.
+ return sArgV0;
+ }
+
+ /**
+ * 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.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final native void sendSignalQuiet(int pid, int signal);
+
+ /**
+ * @return The advertised memory of the system, as the end user would encounter in a retail
+ * display environment. If the advertised memory is not defined, it returns
+ * {@code getTotalMemory()} rounded.
+ *
+ * @hide
+ */
+ public static final long getAdvertisedMem() {
+ String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB");
+ long memSize = FileUtils.parseSize(formatSize);
+
+ if (memSize <= 0) {
+ return FileUtils.roundStorageSize(getTotalMemory());
+ }
+
+ return memSize;
+ }
+
+ /** @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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_TERM_MASK = 0xff;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_ZERO_TERM = 0;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_SPACE_TERM = (int)' ';
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_TAB_TERM = (int)'\t';
+ /** @hide */
+ public static final int PROC_NEWLINE_TERM = (int) '\n';
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_COMBINE = 0x100;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_PARENS = 0x200;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_QUOTES = 0x400;
+ /** @hide */
+ public static final int PROC_CHAR = 0x800;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_OUT_STRING = 0x1000;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROC_OUT_LONG = 0x2000;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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);
+
+ /**
+ * Send a signal to all processes in a group under the given PID, but do not wait for the
+ * processes to be fully cleaned up, or for the cgroup to be removed before returning.
+ * Callers should also ensure that killProcessGroup is called later to ensure the cgroup is
+ * fully removed, otherwise system resources may leak.
+ * @hide
+ */
+ public static final native int sendSignalToProcessGroup(int uid, int pid, int signal);
+
+ /**
+ * Freeze the cgroup for the given UID.
+ * This cgroup may contain child cgroups which will also be frozen. If this cgroup or its
+ * children contain processes with Binder interfaces, those interfaces should be frozen before
+ * the cgroup to avoid blocking synchronous callers indefinitely.
+ *
+ * @param uid The UID to be frozen
+ * @param freeze true = freeze; false = unfreeze
+ * @hide
+ */
+ public static final native void freezeCgroupUid(int uid, boolean freeze);
+
+ /**
+ * 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);
+ }
+
+ }
+
+ /**
+ * Wait for the death of the given process.
+ *
+ * @param pid The process ID to be waited on
+ * @param timeout The maximum time to wait in milliseconds, or -1 to wait forever
+ * @hide
+ */
+ public static void waitForProcessDeath(int pid, int timeout)
+ throws InterruptedException, TimeoutException {
+ boolean fallback = supportsPidFd();
+ if (!fallback) {
+ FileDescriptor pidfd = null;
+ try {
+ final int fd = nativePidFdOpen(pid, 0);
+ if (fd >= 0) {
+ pidfd = new FileDescriptor();
+ pidfd.setInt$(fd);
+ } else {
+ fallback = true;
+ }
+ if (pidfd != null) {
+ StructPollfd[] fds = new StructPollfd[] {
+ new StructPollfd()
+ };
+ fds[0].fd = pidfd;
+ fds[0].events = (short) OsConstants.POLLIN;
+ fds[0].revents = 0;
+ fds[0].userData = null;
+ int res = Os.poll(fds, timeout);
+ if (res > 0) {
+ return;
+ } else if (res == 0) {
+ throw new TimeoutException();
+ } else {
+ // We should get an ErrnoException now
+ }
+ }
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.EINTR) {
+ throw new InterruptedException();
+ }
+ fallback = true;
+ } finally {
+ if (pidfd != null) {
+ IoUtils.closeQuietly(pidfd);
+ }
+ }
+ }
+ if (fallback) {
+ boolean infinity = timeout < 0;
+ long now = System.currentTimeMillis();
+ final long end = now + timeout;
+ while (infinity || now < end) {
+ try {
+ Os.kill(pid, 0);
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.ESRCH) {
+ return;
+ }
+ }
+ Thread.sleep(1);
+ now = System.currentTimeMillis();
+ }
+ }
+ throw new TimeoutException();
+ }
+
+ /**
+ * Determine whether the system supports pidfd APIs
+ *
+ * @return Returns true if the system supports pidfd APIs
+ * @hide
+ */
+ public static boolean supportsPidFd() {
+ if (sPidFdSupported == PIDFD_UNKNOWN) {
+ int fd = -1;
+ try {
+ fd = nativePidFdOpen(myPid(), 0);
+ sPidFdSupported = PIDFD_SUPPORTED;
+ } catch (ErrnoException e) {
+ sPidFdSupported = e.errno != OsConstants.ENOSYS
+ ? PIDFD_SUPPORTED : PIDFD_UNSUPPORTED;
+ } finally {
+ if (fd >= 0) {
+ final FileDescriptor f = new FileDescriptor();
+ f.setInt$(fd);
+ IoUtils.closeQuietly(f);
+ }
+ }
+ }
+ return sPidFdSupported == PIDFD_SUPPORTED;
+ }
+
+ /**
+ * Open process file descriptor for given pid.
+ *
+ * @param pid The process ID to open for
+ * @param flags Reserved, unused now, must be 0
+ * @return The process file descriptor for given pid
+ * @throws IOException if it can't be opened
+ *
+ * @hide
+ */
+ public static @Nullable FileDescriptor openPidFd(int pid, int flags) throws IOException {
+ if (!supportsPidFd()) {
+ return null;
+ }
+ if (flags != 0) {
+ throw new IllegalArgumentException();
+ }
+ try {
+ FileDescriptor pidfd = new FileDescriptor();
+ pidfd.setInt$(nativePidFdOpen(pid, flags));
+ return pidfd;
+ } catch (ErrnoException e) {
+ IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ private static native int nativePidFdOpen(int pid, int flags) throws ErrnoException;
+}
diff --git a/android-34/android/os/ProxyFileDescriptorCallback.java b/android-34/android/os/ProxyFileDescriptorCallback.java
new file mode 100644
index 0000000..9f56802
--- /dev/null
+++ b/android-34/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-34/android/os/PssPerfTest.java b/android-34/android/os/PssPerfTest.java
new file mode 100644
index 0000000..2cc294f
--- /dev/null
+++ b/android-34/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-34/android/os/RecoverySystem.java b/android-34/android/os/RecoverySystem.java
new file mode 100644
index 0000000..a3b836a
--- /dev/null
+++ b/android-34/android/os/RecoverySystem.java
@@ -0,0 +1,1528 @@
+/*
+ * 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 android.view.Display.DEFAULT_DISPLAY;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.provider.Settings;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.Display;
+
+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.List;
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+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
+
+ private static final long DEFAULT_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS =
+ 45000L; // 45 s
+ private static final long MIN_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS = 15000L; // 15 s
+ private static final long MAX_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS = 90000L; // 90 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";
+ private static final String ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS =
+ "com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS";
+
+ /**
+ * Used in {@link #wipeEuiccData} & {@link #removeEuiccInvisibleSubs} as package name of
+ * callback intent.
+ */
+ private static final String PACKAGE_NAME_EUICC_DATA_MANAGEMENT_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;
+
+ /**
+ * The error codes for reboots initiated by resume on reboot clients.
+ * @hide
+ */
+ @IntDef(prefix = { "RESUME_ON_REBOOT_REBOOT_ERROR_" }, value = {
+ RESUME_ON_REBOOT_REBOOT_ERROR_NONE,
+ RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED,
+ RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME,
+ RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED,
+ RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH,
+ RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE})
+ public @interface ResumeOnRebootRebootErrorCode {}
+
+ /**
+ * The preparation of resume on reboot succeeds.
+ *
+ * <p> Don't expose it because a successful reboot should just reboot the device.
+ * @hide
+ */
+ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_NONE = 0;
+
+ /**
+ * The resume on reboot fails due to an unknown reason.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED = 1000;
+
+ /**
+ * The resume on reboot fails because the package name of the client is invalid, e.g. null
+ * packageName, name contains invalid characters, etc.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME = 2000;
+
+ /**
+ * The resume on reboot fails because the Lock Screen Knowledge Factor hasn't been captured.
+ * This error is also reported if the client attempts to reboot without preparing RoR.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED = 3000;
+
+ /**
+ * The resume on reboot fails because the client expects a different boot slot for the next boot
+ * on A/B devices.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH = 4000;
+
+ /**
+ * The resume on reboot fails because the resume on reboot provider, e.g. HAL / server based,
+ * fails to arm/store the escrow key.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE = 5000;
+
+ /**
+ * 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("RequiresPermission")
+ 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");
+ }
+ try {
+ if (!rs.allocateSpaceForUpdate(packageFile)) {
+ rs.clearBcb();
+ throw new IOException("Failed to allocate space for update "
+ + packageFile.getAbsolutePath());
+ }
+ } catch (RemoteException e) {
+ rs.clearBcb();
+ e.rethrowAsRuntimeException();
+ }
+
+ // 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)) {
+ DisplayManager dm = context.getSystemService(DisplayManager.class);
+ if (dm.getDisplay(DEFAULT_DISPLAY).getState() != Display.STATE_ON) {
+ reason += ",quiescent";
+ }
+ }
+ pm.reboot(reason);
+
+ throw new IOException("Reboot failed (no permissions?)");
+ }
+ }
+
+ /**
+ * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge
+ * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup
+ * and ready to apply the OTA. <p>
+ *
+ * <p> If the device doesn't setup a lock screen, i.e. by checking
+ * {@link KeyguardManager#isKeyguardSecure()}, this API call will fail and throw an exception.
+ * Callers are expected to use {@link PowerManager#reboot(String)} directly without going
+ * through the RoR flow. <p>
+ *
+ * <p> This API is expected to handle requests from multiple clients simultaneously, e.g.
+ * from ota and mainline. The behavior of multi-client Resume on Reboot works as follows
+ * <li> Each client should call this function to prepare for Resume on Reboot before calling
+ * {@link #rebootAndApply(Context, String, boolean)} </li>
+ * <li> One client cannot clear the Resume on Reboot preparation of another client. </li>
+ * <li> If multiple clients have prepared for Resume on Reboot, the subsequent reboot will be
+ * first come, first served. </li>
+ *
+ * @param context the Context to use.
+ * @param updateToken this parameter is deprecated and won't be used. Callers can supply with
+ * an empty string. See details in
+ * <a href="http://go/multi-client-ror">http://go/multi-client-ror</a>
+ * TODO(xunchang) update the link of document with the public doc.
+ * @param intentSender the intent to call when the update is prepared; may be {@code null}
+ * @throws IOException if there were any errors setting up unattended update
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
+ public static void prepareForUnattendedUpdate(@NonNull Context context,
+ @NonNull String updateToken, @Nullable IntentSender intentSender) throws IOException {
+ if (updateToken == null) {
+ throw new NullPointerException("updateToken == null");
+ }
+
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (keyguardManager == null || !keyguardManager.isDeviceSecure()) {
+ throw new IOException("Failed to request LSKF because the device doesn't have a"
+ + " lock screen. ");
+ }
+
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ if (!rs.requestLskf(context.getPackageName(), intentSender)) {
+ throw new IOException("preparation for update failed");
+ }
+ }
+
+ /**
+ * Request that any previously requested Lock Screen Knowledge Factor (LSKF) is cleared and
+ * the preparation for unattended update is reset.
+ *
+ * <p> Note that the API won't clear the underlying Resume on Reboot preparation state if
+ * another client has requested. So the reboot call from the other client can still succeed.
+ *
+ * @param context the Context to use.
+ * @throws IOException if there were any errors clearing the unattended update state
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
+ public static void clearPrepareForUnattendedUpdate(@NonNull Context context)
+ throws IOException {
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ if (!rs.clearLskf(context.getPackageName())) {
+ throw new IOException("could not reset unattended update state");
+ }
+ }
+
+ /**
+ * Request that the device reboot and apply the update that has been prepared. This API is
+ * deprecated, and is expected to be used by OTA only on devices running Android 11.
+ *
+ * @param context the Context to use.
+ * @param updateToken this parameter is deprecated and won't be used. See details in
+ * <a href="http://go/multi-client-ror">http://go/multi-client-ror</a>
+ * TODO(xunchang) update the link of document with the public doc.
+ * @param reason the reboot reason to give to the {@link PowerManager}
+ * @throws IOException if the reboot couldn't proceed because the device wasn't ready for an
+ * unattended reboot or if the {@code updateToken} did not match the previously
+ * given token
+ * @hide
+ * @deprecated Use {@link #rebootAndApply(Context, String, boolean)} instead
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static void rebootAndApply(@NonNull Context context, @NonNull String updateToken,
+ @NonNull String reason) throws IOException {
+ if (updateToken == null) {
+ throw new NullPointerException("updateToken == null");
+ }
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ // OTA is the sole user, who expects a slot switch.
+ if (rs.rebootWithLskfAssumeSlotSwitch(context.getPackageName(), reason)
+ != RESUME_ON_REBOOT_REBOOT_ERROR_NONE) {
+ throw new IOException("system not prepared to apply update");
+ }
+ }
+
+ /**
+ * Query if Resume on Reboot has been prepared for a given caller.
+ *
+ * @param context the Context to use.
+ * @throws IOException if there were any errors connecting to the service or querying the state.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
+ public static boolean isPreparedForUnattendedUpdate(@NonNull Context context)
+ throws IOException {
+ RecoverySystem rs = context.getSystemService(RecoverySystem.class);
+ return rs.isLskfCaptured(context.getPackageName());
+ }
+
+ /**
+ * Request that the device reboot and apply the update that has been prepared.
+ * {@link #prepareForUnattendedUpdate} must be called before for the given client,
+ * otherwise the function call will fail.
+ *
+ * @param context the Context to use.
+ * @param reason the reboot reason to give to the {@link PowerManager}
+ * @param slotSwitch true if the caller expects the slot to be switched on A/B devices.
+ *
+ * @return 0 on success, and a non-zero error code if the reboot couldn't proceed because the
+ * device wasn't ready for an unattended reboot.
+ * @throws IOException on remote exceptions from the RecoverySystemService
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
+ public static @ResumeOnRebootRebootErrorCode int rebootAndApply(@NonNull Context context,
+ @NonNull String reason, boolean slotSwitch) throws IOException {
+ RecoverySystem rs = context.getSystemService(RecoverySystem.class);
+ return rs.rebootWithLskf(context.getPackageName(), reason, slotSwitch);
+ }
+
+ /**
+ * 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();
+
+ EuiccManager euiccManager = context.getSystemService(EuiccManager.class);
+ if (wipeEuicc) {
+ wipeEuiccData(context, PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK);
+ } else {
+ removeEuiccInvisibleSubs(context, euiccManager);
+ }
+
+ 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_IMMUTABLE | 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;
+ }
+
+ private static void removeEuiccInvisibleSubs(
+ Context context, EuiccManager euiccManager) {
+ ContentResolver cr = context.getContentResolver();
+ if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) {
+ // If the eUICC isn't provisioned, there's no need to remove euicc invisible profiles,
+ // as there's nothing to be removed.
+ Log.i(TAG, "Skip removing eUICC invisible profiles as it is not provisioned.");
+ return;
+ } else if (euiccManager == null || !euiccManager.isEnabled()) {
+ Log.i(TAG, "Skip removing eUICC invisible profiles as eUICC manager is not available.");
+ return;
+ }
+ SubscriptionManager subscriptionManager =
+ context.getSystemService(SubscriptionManager.class);
+ List<SubscriptionInfo> availableSubs =
+ subscriptionManager.getAvailableSubscriptionInfoList();
+ if (availableSubs == null || availableSubs.isEmpty()) {
+ Log.i(TAG, "Skip removing eUICC invisible profiles as no available profiles found.");
+ return;
+ }
+ List<SubscriptionInfo> invisibleSubs = new ArrayList<>();
+ for (SubscriptionInfo sub : availableSubs) {
+ if (sub.isEmbedded() && sub.getGroupUuid() != null && sub.isOpportunistic()) {
+ invisibleSubs.add(sub);
+ }
+ }
+ removeEuiccInvisibleSubs(context, invisibleSubs, euiccManager);
+ }
+
+ private static boolean removeEuiccInvisibleSubs(
+ Context context, List<SubscriptionInfo> subscriptionInfos, EuiccManager euiccManager) {
+ if (subscriptionInfos == null || subscriptionInfos.isEmpty()) {
+ Log.i(TAG, "There are no eUICC invisible profiles needed to be removed.");
+ return true;
+ }
+ CountDownLatch removeSubsLatch = new CountDownLatch(subscriptionInfos.size());
+ final AtomicInteger removedSubsCount = new AtomicInteger(0);
+
+ BroadcastReceiver removeEuiccSubsReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS.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 removing euicc opportunistic profile, Detailed code = "
+ + detailedCode);
+ } else {
+ Log.e(TAG, "Successfully remove euicc opportunistic profile.");
+ removedSubsCount.incrementAndGet();
+ }
+ removeSubsLatch.countDown();
+ }
+ }
+ };
+
+ Intent intent = new Intent(ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS);
+ intent.setPackage(PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK);
+ PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ UserHandle.SYSTEM);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS);
+ HandlerThread euiccHandlerThread =
+ new HandlerThread("euiccRemovingSubsReceiverThread");
+ euiccHandlerThread.start();
+ Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
+ context.getApplicationContext()
+ .registerReceiver(
+ removeEuiccSubsReceiver, intentFilter, null, euiccHandler);
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
+ Log.i(
+ TAG,
+ "Remove invisible subscription " + subscriptionInfo.getSubscriptionId()
+ + " from card " + subscriptionInfo.getCardId());
+ euiccManager.createForCardId(subscriptionInfo.getCardId())
+ .deleteSubscription(subscriptionInfo.getSubscriptionId(), callbackIntent);
+ }
+ try {
+ long waitingTimeMillis = Settings.Global.getLong(
+ context.getContentResolver(),
+ Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
+ DEFAULT_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS);
+ if (waitingTimeMillis < MIN_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS) {
+ waitingTimeMillis = MIN_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS;
+ } else if (waitingTimeMillis > MAX_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS) {
+ waitingTimeMillis = MAX_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS;
+ }
+ if (!removeSubsLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
+ Log.e(TAG, "Timeout removing invisible euicc profiles.");
+ return false;
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.e(TAG, "Removing invisible euicc profiles interrupted", e);
+ return false;
+ } finally {
+ context.getApplicationContext().unregisterReceiver(removeEuiccSubsReceiver);
+ if (euiccHandlerThread != null) {
+ euiccHandlerThread.quit();
+ }
+ }
+ return removedSubsCount.get() == subscriptionInfos.size();
+ }
+
+ /** {@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 allocate space
+ */
+ private boolean allocateSpaceForUpdate(File packageFile) throws RemoteException {
+ return mService.allocateSpaceForUpdate(packageFile.getAbsolutePath());
+ }
+
+ /**
+ * 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) {
+ }
+ }
+
+ /**
+ * Begins the process of asking the user for the Lock Screen Knowledge Factor.
+ *
+ * @param packageName the package name of the caller who requests Resume on Reboot
+ * @return true if the request was correct
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
+ try {
+ return mService.requestLskf(packageName, sender);
+ } catch (RemoteException | SecurityException e) {
+ throw new IOException("could not request LSKF capture", e);
+ }
+ }
+
+ /**
+ * Calls the recovery system service and clears the setup for the OTA.
+ *
+ * @return true if the setup for OTA was cleared
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean clearLskf(String packageName) throws IOException {
+ try {
+ return mService.clearLskf(packageName);
+ } catch (RemoteException | SecurityException e) {
+ throw new IOException("could not clear LSKF", e);
+ }
+ }
+
+ /**
+ * Queries if the Resume on Reboot has been prepared for a given caller.
+ *
+ * @param packageName the identifier of the caller who requests Resume on Reboot
+ * @return true if Resume on Reboot is prepared.
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean isLskfCaptured(String packageName) throws IOException {
+ try {
+ return mService.isLskfCaptured(packageName);
+ } catch (RemoteException | SecurityException e) {
+ throw new IOException("could not get LSKF capture state", e);
+ }
+ }
+
+ /**
+ * Calls the recovery system service to reboot and apply update.
+ *
+ */
+ private @ResumeOnRebootRebootErrorCode int rebootWithLskf(String packageName, String reason,
+ boolean slotSwitch) throws IOException {
+ try {
+ return mService.rebootWithLskf(packageName, reason, slotSwitch);
+ } catch (RemoteException | SecurityException e) {
+ throw new IOException("could not reboot for update", e);
+ }
+ }
+
+ /**
+ * Calls the recovery system service to reboot and apply update. This is the legacy API and
+ * expects a slot switch for A/B devices.
+ *
+ */
+ private @ResumeOnRebootRebootErrorCode int rebootWithLskfAssumeSlotSwitch(String packageName,
+ String reason) throws IOException {
+ try {
+ return mService.rebootWithLskfAssumeSlotSwitch(packageName, reason);
+ } catch (RemoteException | RuntimeException e) {
+ throw new IOException("could not reboot for update", e);
+ }
+ }
+
+ /**
+ * 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-34/android/os/RedactingFileDescriptor.java b/android-34/android/os/RedactingFileDescriptor.java
new file mode 100644
index 0000000..a1ed214
--- /dev/null
+++ b/android-34/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-34/android/os/Registrant.java b/android-34/android/os/Registrant.java
new file mode 100644
index 0000000..bde7ec1
--- /dev/null
+++ b/android-34/android/os/Registrant.java
@@ -0,0 +1,126 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+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;
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public Handler
+ getHandler()
+ {
+ if (refH == null)
+ return null;
+
+ return (Handler) refH.get();
+ }
+
+ WeakReference refH;
+ int what;
+ Object userObj;
+}
diff --git a/android-34/android/os/RegistrantList.java b/android-34/android/os/RegistrantList.java
new file mode 100644
index 0000000..b36734b
--- /dev/null
+++ b/android-34/android/os/RegistrantList.java
@@ -0,0 +1,144 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+import java.util.ArrayList;
+
+/** @hide */
+public class RegistrantList
+{
+ ArrayList registrants = new ArrayList(); // of Registrant
+
+ @UnsupportedAppUsage
+ public RegistrantList() {
+ }
+
+ @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);
+ }
+ }
+ }
+
+ public synchronized void removeAll() {
+ registrants.clear();
+ }
+
+ @UnsupportedAppUsage
+ public synchronized int
+ size()
+ {
+ return registrants.size();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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-34/android/os/RemoteCallback.java b/android-34/android/os/RemoteCallback.java
new file mode 100644
index 0000000..49f84ad
--- /dev/null
+++ b/android-34/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.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class RemoteCallback implements Parcelable {
+
+ public interface OnResultListener {
+ void onResult(@Nullable Bundle result);
+ }
+
+ private final OnResultListener mListener;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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-34/android/os/RemoteCallbackList.java b/android-34/android/os/RemoteCallbackList.java
new file mode 100644
index 0000000..d89c3d5
--- /dev/null
+++ b/android-34/android/os/RemoteCallbackList.java
@@ -0,0 +1,470 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.function.BiConsumer;
+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);
+ unregister(callback);
+ 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();
+ }
+ }
+
+ /**
+ * Performs {@code action} on each callback and associated cookie, calling {@link
+ * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping.
+ *
+ * @hide
+ */
+ public <C> void broadcast(BiConsumer<E, C> action) {
+ int itemCount = beginBroadcast();
+ try {
+ for (int i = 0; i < itemCount; i++) {
+ action.accept(getBroadcastItem(i), (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-34/android/os/RemoteException.java b/android-34/android/os/RemoteException.java
new file mode 100644
index 0000000..970f419
--- /dev/null
+++ b/android-34/android/os/RemoteException.java
@@ -0,0 +1,87 @@
+/*
+ * 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.util.AndroidException;
+
+/**
+ * Parent exception for all Binder remote-invocation errors
+ *
+ * Note: not all exceptions from binder services will be subclasses of this.
+ * For instance, RuntimeException and several subclasses of it may be
+ * thrown as well as OutOfMemoryException.
+ *
+ * One common subclass is {@link DeadObjectException}.
+ */
+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 RemoteException(Throwable cause) {
+ this(cause.getMessage(), cause, true, false);
+ }
+
+ /**
+ * Rethrow this as an unchecked runtime exception.
+ * <p>
+ * Apps making calls into other processes 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.
+ *
+ * @throws RuntimeException
+ */
+ @NonNull
+ 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 DeadSystemRuntimeException} 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.
+ *
+ * @throws RuntimeException
+ */
+ @NonNull
+ public RuntimeException rethrowFromSystemServer() {
+ if (this instanceof DeadObjectException) {
+ throw new DeadSystemRuntimeException();
+ } else {
+ throw new RuntimeException(this);
+ }
+ }
+}
diff --git a/android-34/android/os/RemoteMailException.java b/android-34/android/os/RemoteMailException.java
new file mode 100644
index 0000000..1ac96d1
--- /dev/null
+++ b/android-34/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-34/android/os/ResultReceiver.java b/android-34/android/os/ResultReceiver.java
new file mode 100644
index 0000000..f2d8fe4
--- /dev/null
+++ b/android-34/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-34/android/os/RevocableFileDescriptor.java b/android-34/android/os/RevocableFileDescriptor.java
new file mode 100644
index 0000000..ac2cd60
--- /dev/null
+++ b/android-34/android/os/RevocableFileDescriptor.java
@@ -0,0 +1,174 @@
+/*
+ * 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;
+
+ private ParcelFileDescriptor.OnCloseListener mOnCloseListener;
+
+ /** {@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);
+ }
+
+ /**
+ * Callback for indicating that {@link ParcelFileDescriptor} passed to the client
+ * process ({@link #getRevocableFileDescriptor()}) has been closed.
+ */
+ public void addOnCloseListener(ParcelFileDescriptor.OnCloseListener onCloseListener) {
+ mOnCloseListener = onCloseListener;
+ }
+
+ 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);
+ if (mOnCloseListener != null) {
+ mOnCloseListener.onClose(null);
+ }
+ }
+ };
+}
diff --git a/android-34/android/os/SELinux.java b/android-34/android/os/SELinux.java
new file mode 100644
index 0000000..f64a811
--- /dev/null
+++ b/android-34/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.compat.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-34/android/os/ServiceManager.java b/android-34/android/os/ServiceManager.java
new file mode 100644
index 0000000..b210c46
--- /dev/null
+++ b/android-34/android/os/ServiceManager.java
@@ -0,0 +1,429 @@
+/*
+ * 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.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.util.StatLogger;
+
+import java.util.Map;
+
+/**
+ * Manage binder services as registered with the binder context manager. These services must be
+ * declared statically on an Android device (SELinux access_vector service_manager, w/ service
+ * names in service_contexts files), and they do not follow the activity lifecycle. When
+ * building applications, android.app.Service should be preferred.
+ *
+ * @hide
+ **/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ServiceManager {
+ private static final String TAG = "ServiceManager";
+ private static final Object sLock = new Object();
+
+ @UnsupportedAppUsage
+ private static IServiceManager sServiceManager;
+
+ /**
+ * Cache for the "well known" services, such as WM and AM.
+ */
+ @UnsupportedAppUsage
+ private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
+
+ /**
+ * We do the "slow log" at most once every this interval.
+ */
+ private static final int SLOW_LOG_INTERVAL_MS = 5000;
+
+ /**
+ * We do the "stats log" at most once every this interval.
+ */
+ private static final int STATS_LOG_INTERVAL_MS = 5000;
+
+ /**
+ * Threshold in uS for a "slow" call, used on core UIDs. We use a more relax value to
+ * avoid logspam.
+ */
+ private static final long GET_SERVICE_SLOW_THRESHOLD_US_CORE =
+ SystemProperties.getInt("debug.servicemanager.slow_call_core_ms", 10) * 1000;
+
+ /**
+ * Threshold in uS for a "slow" call, used on non-core UIDs. We use a more relax value to
+ * avoid logspam.
+ */
+ private static final long GET_SERVICE_SLOW_THRESHOLD_US_NON_CORE =
+ SystemProperties.getInt("debug.servicemanager.slow_call_ms", 50) * 1000;
+
+ /**
+ * We log stats logging ever this many getService() calls.
+ */
+ private static final int GET_SERVICE_LOG_EVERY_CALLS_CORE =
+ SystemProperties.getInt("debug.servicemanager.log_calls_core", 100);
+
+ /**
+ * We log stats logging ever this many getService() calls.
+ */
+ private static final int GET_SERVICE_LOG_EVERY_CALLS_NON_CORE =
+ SystemProperties.getInt("debug.servicemanager.log_calls", 200);
+
+ @GuardedBy("sLock")
+ private static int sGetServiceAccumulatedUs;
+
+ @GuardedBy("sLock")
+ private static int sGetServiceAccumulatedCallCount;
+
+ @GuardedBy("sLock")
+ private static long sLastStatsLogUptime;
+
+ @GuardedBy("sLock")
+ private static long sLastSlowLogUptime;
+
+ @GuardedBy("sLock")
+ private static long sLastSlowLogActualTime;
+
+ interface Stats {
+ int GET_SERVICE = 0;
+
+ int COUNT = GET_SERVICE + 1;
+ }
+
+ /** @hide */
+ public static final StatLogger sStatLogger = new StatLogger(new String[] {
+ "getService()",
+ });
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public ServiceManager() {
+ }
+
+ @UnsupportedAppUsage
+ private static IServiceManager getIServiceManager() {
+ if (sServiceManager != null) {
+ return sServiceManager;
+ }
+
+ // Find the service manager
+ sServiceManager = ServiceManagerNative
+ .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
+ return sServiceManager;
+ }
+
+ /**
+ * 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
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static IBinder getService(String name) {
+ try {
+ IBinder service = sCache.get(name);
+ if (service != null) {
+ return service;
+ } else {
+ return Binder.allowBlocking(rawGetService(name));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in getService", e);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a reference to a service with the given name, or throws
+ * {@link ServiceNotFoundException} if none is found.
+ *
+ * @hide
+ */
+ public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
+ final IBinder binder = getService(name);
+ if (binder != null) {
+ return binder;
+ } else {
+ 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
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static void addService(String name, IBinder service) {
+ addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
+ }
+
+ /**
+ * 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
+ * @param allowIsolated set to true to allow isolated sandboxed processes
+ * to access this service
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static void addService(String name, IBinder service, boolean allowIsolated) {
+ addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
+ }
+
+ /**
+ * 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
+ * @param allowIsolated set to true to allow isolated sandboxed processes
+ * @param dumpPriority supported dump priority levels as a bitmask
+ * to access this service
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static void addService(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ try {
+ getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in addService", e);
+ }
+ }
+
+ /**
+ * Retrieve an existing service called @a name from the
+ * service manager. Non-blocking.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static IBinder checkService(String name) {
+ try {
+ IBinder service = sCache.get(name);
+ if (service != null) {
+ return service;
+ } else {
+ return Binder.allowBlocking(getIServiceManager().checkService(name));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in checkService", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns whether the specified service is declared.
+ *
+ * @return true if the service is declared somewhere (eg. VINTF manifest) and
+ * waitForService should always be able to return the service.
+ */
+ public static boolean isDeclared(@NonNull String name) {
+ try {
+ return getIServiceManager().isDeclared(name);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in isDeclared", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns an array of all declared instances for a particular interface.
+ *
+ * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+ * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+ * returned.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public static String[] getDeclaredInstances(@NonNull String iface) {
+ try {
+ return getIServiceManager().getDeclaredInstances(iface);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in getDeclaredInstances", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the specified service from the service manager.
+ *
+ * If the service is not running, servicemanager will attempt to start it, and this function
+ * will wait for it to be ready.
+ *
+ * @return {@code null} only if there are permission problems or fatal errors.
+ * @hide
+ */
+ public static IBinder waitForService(@NonNull String name) {
+ return Binder.allowBlocking(waitForServiceNative(name));
+ }
+
+ private static native IBinder waitForServiceNative(@NonNull String name);
+
+ /**
+ * Returns the specified service from the service manager, if declared.
+ *
+ * If the service is not running, servicemanager will attempt to start it, and this function
+ * will wait for it to be ready.
+ *
+ * @throws SecurityException if the process does not have the permissions to check
+ * isDeclared() for the service.
+ * @return {@code null} if the service is not declared in the manifest, or if there
+ * are fatal errors.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @Nullable public static IBinder waitForDeclaredService(@NonNull String name) {
+ return isDeclared(name) ? waitForService(name) : null;
+ }
+
+ /**
+ * Register callback for service registration notifications.
+ *
+ * @throws RemoteException for underlying error.
+ * @hide
+ */
+ public static void registerForNotifications(
+ @NonNull String name, @NonNull IServiceCallback callback) throws RemoteException {
+ getIServiceManager().registerForNotifications(name, callback);
+ }
+
+ /**
+ * 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
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static String[] listServices() {
+ try {
+ return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in listServices", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get service debug info.
+ * @return an array of information for each service (like listServices, but with PIDs)
+ * @hide
+ */
+ public static ServiceDebugInfo[] getServiceDebugInfo() {
+ try {
+ return getIServiceManager().getServiceDebugInfo();
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in getServiceDebugInfo", e);
+ 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) {
+ if (sCache.size() != 0) {
+ throw new IllegalStateException("setServiceCache may only be called once");
+ }
+ sCache.putAll(cache);
+ }
+
+ /**
+ * 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 {
+ public ServiceNotFoundException(String name) {
+ super("No service published for: " + name);
+ }
+ }
+
+ private static IBinder rawGetService(String name) throws RemoteException {
+ final long start = sStatLogger.getTime();
+
+ final IBinder binder = getIServiceManager().getService(name);
+
+ final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
+
+ final int myUid = Process.myUid();
+ final boolean isCore = UserHandle.isCore(myUid);
+
+ final long slowThreshold = isCore
+ ? GET_SERVICE_SLOW_THRESHOLD_US_CORE
+ : GET_SERVICE_SLOW_THRESHOLD_US_NON_CORE;
+
+ synchronized (sLock) {
+ sGetServiceAccumulatedUs += time;
+ sGetServiceAccumulatedCallCount++;
+
+ final long nowUptime = SystemClock.uptimeMillis();
+
+ // Was a slow call?
+ if (time >= slowThreshold) {
+ // We do a slow log:
+ // - At most once in every SLOW_LOG_INTERVAL_MS
+ // - OR it was slower than the previously logged slow call.
+ if ((nowUptime > (sLastSlowLogUptime + SLOW_LOG_INTERVAL_MS))
+ || (sLastSlowLogActualTime < time)) {
+ EventLogTags.writeServiceManagerSlow(time / 1000, name);
+
+ sLastSlowLogUptime = nowUptime;
+ sLastSlowLogActualTime = time;
+ }
+ }
+
+ // Every GET_SERVICE_LOG_EVERY_CALLS calls, log the total time spent in getService().
+
+ final int logInterval = isCore
+ ? GET_SERVICE_LOG_EVERY_CALLS_CORE
+ : GET_SERVICE_LOG_EVERY_CALLS_NON_CORE;
+
+ if ((sGetServiceAccumulatedCallCount >= logInterval)
+ && (nowUptime >= (sLastStatsLogUptime + STATS_LOG_INTERVAL_MS))) {
+
+ EventLogTags.writeServiceManagerStats(
+ sGetServiceAccumulatedCallCount, // Total # of getService() calls.
+ sGetServiceAccumulatedUs / 1000, // Total time spent in getService() calls.
+ (int) (nowUptime - sLastStatsLogUptime)); // Uptime duration since last log.
+ sGetServiceAccumulatedCallCount = 0;
+ sGetServiceAccumulatedUs = 0;
+ sLastStatsLogUptime = nowUptime;
+ }
+ }
+ return binder;
+ }
+}
diff --git a/android-34/android/os/ServiceManagerNative.java b/android-34/android/os/ServiceManagerNative.java
new file mode 100644
index 0000000..f2143f6
--- /dev/null
+++ b/android-34/android/os/ServiceManagerNative.java
@@ -0,0 +1,131 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Native implementation of the service manager. Most clients will only
+ * care about asInterface().
+ *
+ * @hide
+ */
+public final class ServiceManagerNative {
+ private ServiceManagerNative() {}
+
+ /**
+ * Cast a Binder object into a service manager interface, generating
+ * a proxy if needed.
+ *
+ * TODO: delete this method and have clients use
+ * IServiceManager.Stub.asInterface instead
+ */
+ @UnsupportedAppUsage
+ public static IServiceManager asInterface(IBinder obj) {
+ if (obj == null) {
+ return null;
+ }
+
+ // ServiceManager is never local
+ return new ServiceManagerProxy(obj);
+ }
+}
+
+// This class should be deleted and replaced with IServiceManager.Stub whenever
+// mRemote is no longer used
+class ServiceManagerProxy implements IServiceManager {
+ public ServiceManagerProxy(IBinder remote) {
+ mRemote = remote;
+ mServiceManager = IServiceManager.Stub.asInterface(remote);
+ }
+
+ public IBinder asBinder() {
+ return mRemote;
+ }
+
+ @UnsupportedAppUsage
+ public IBinder getService(String name) throws RemoteException {
+ // Same as checkService (old versions of servicemanager had both methods).
+ return mServiceManager.checkService(name);
+ }
+
+ public IBinder checkService(String name) throws RemoteException {
+ return mServiceManager.checkService(name);
+ }
+
+ public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
+ throws RemoteException {
+ mServiceManager.addService(name, service, allowIsolated, dumpPriority);
+ }
+
+ public String[] listServices(int dumpPriority) throws RemoteException {
+ return mServiceManager.listServices(dumpPriority);
+ }
+
+ public void registerForNotifications(String name, IServiceCallback cb)
+ throws RemoteException {
+ mServiceManager.registerForNotifications(name, cb);
+ }
+
+ public void unregisterForNotifications(String name, IServiceCallback cb)
+ throws RemoteException {
+ throw new RemoteException();
+ }
+
+ public boolean isDeclared(String name) throws RemoteException {
+ return mServiceManager.isDeclared(name);
+ }
+
+ public String[] getDeclaredInstances(String iface) throws RemoteException {
+ return mServiceManager.getDeclaredInstances(iface);
+ }
+
+ public String updatableViaApex(String name) throws RemoteException {
+ return mServiceManager.updatableViaApex(name);
+ }
+
+ public String[] getUpdatableNames(String apexName) throws RemoteException {
+ return mServiceManager.getUpdatableNames(apexName);
+ }
+
+ public ConnectionInfo getConnectionInfo(String name) throws RemoteException {
+ return mServiceManager.getConnectionInfo(name);
+ }
+
+ public void registerClientCallback(String name, IBinder service, IClientCallback cb)
+ throws RemoteException {
+ throw new RemoteException();
+ }
+
+ public void tryUnregisterService(String name, IBinder service) throws RemoteException {
+ throw new RemoteException();
+ }
+
+ public ServiceDebugInfo[] getServiceDebugInfo() throws RemoteException {
+ return mServiceManager.getServiceDebugInfo();
+ }
+
+ /**
+ * Same as mServiceManager but used by apps.
+ *
+ * Once this can be removed, ServiceManagerProxy should be removed entirely.
+ */
+ @UnsupportedAppUsage
+ private IBinder mRemote;
+
+ private IServiceManager mServiceManager;
+}
diff --git a/android-34/android/os/ServiceSpecificException.java b/android-34/android/os/ServiceSpecificException.java
new file mode 100644
index 0000000..49ce40b
--- /dev/null
+++ b/android-34/android/os/ServiceSpecificException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.NonNull;
+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;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/android-34/android/os/SharedMemory.java b/android-34/android/os/SharedMemory.java
new file mode 100644
index 0000000..cba4423
--- /dev/null
+++ b/android-34/android/os/SharedMemory.java
@@ -0,0 +1,403 @@
+/*
+ * 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.compat.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.io.IOException;
+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.getInt$(), 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");
+ }
+ }
+
+ /**
+ * Creates an instance from existing shared memory passed as {@link ParcelFileDescriptor}.
+ *
+ * <p> The {@code fd} should be a shared memory created from
+ {@code SharedMemory or ASharedMemory}. This can be useful when shared memory is passed as
+ file descriptor through JNI or binder service implemented in cpp.
+ * <p> Note that newly created {@code SharedMemory} takes ownership of passed {@code fd} and
+ * the original {@code fd} becomes detached (Check {@link ParcelFileDescriptor#detachFd()}).
+ * If the caller wants to use the file descriptor after the call, the caller should duplicate
+ * the file descriptor (Check {@link ParcelFileDescriptor#dup()}) and pass the duped version
+ * instead.
+ *
+ * @param fd File descriptor of shared memory passed as {@link ParcelFileDescriptor}.
+ */
+ public static @NonNull SharedMemory fromFileDescriptor(@NonNull ParcelFileDescriptor fd) {
+ FileDescriptor f = new FileDescriptor();
+ f.setInt$(fd.detachFd());
+ return new SharedMemory(f);
+ }
+
+ 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(trackingBug = 171971817)
+ 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() {
+ mFileDescriptor.setInt$(-1);
+ 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);
+ }
+
+ /**
+ * Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor.
+ * This obeys standard POSIX semantics, where the
+ * new file descriptor shared state such as file position with the
+ * original file descriptor.
+ * TODO: propose this method as a public or system API for next release to achieve parity with
+ * NDK ASharedMemory_dupFromJava.
+ *
+ * @hide
+ */
+ public ParcelFileDescriptor getFdDup() throws IOException {
+ return ParcelFileDescriptor.dup(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 int mFd;
+ private MemoryRegistration mMemoryReference;
+
+ private Closer(int fd, MemoryRegistration memoryReference) {
+ mFd = fd;
+ mMemoryReference = memoryReference;
+ }
+
+ @Override
+ public void run() {
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(mFd);
+ Os.close(fd);
+ } 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-34/android/os/SharedPreferencesTest.java b/android-34/android/os/SharedPreferencesTest.java
new file mode 100644
index 0000000..dd479ac
--- /dev/null
+++ b/android-34/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-34/android/os/ShellCallback.java b/android-34/android/os/ShellCallback.java
new file mode 100644
index 0000000..be9fb89
--- /dev/null
+++ b/android-34/android/os/ShellCallback.java
@@ -0,0 +1,126 @@
+/*
+ * 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());
+ }
+ }
+
+ public IBinder getShellCallbackBinder() {
+ return 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-34/android/os/ShellCommand.java b/android-34/android/os/ShellCommand.java
new file mode 100644
index 0000000..a2173a6
--- /dev/null
+++ b/android-34/android/os/ShellCommand.java
@@ -0,0 +1,106 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.util.Slog;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.FileDescriptor;
+
+/**
+ * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
+ * @hide
+ */
+public abstract class ShellCommand extends BasicShellCommandHandler {
+ private ShellCallback mShellCallback;
+ private ResultReceiver mResultReceiver;
+
+ public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ mShellCallback = callback;
+ mResultReceiver = resultReceiver;
+ final int result = super.exec(target, in, out, err, args);
+
+ if (mResultReceiver != null) {
+ mResultReceiver.send(result, null);
+ }
+
+ return result;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ public int handleDefaultCommands(String cmd) {
+ if ("dump".equals(cmd)) {
+ String[] newArgs = new String[getAllArgs().length-1];
+ System.arraycopy(getAllArgs(), 1, newArgs, 0, getAllArgs().length-1);
+ getTarget().doDump(getOutFileDescriptor(), getOutPrintWriter(), newArgs);
+ return 0;
+ }
+ return super.handleDefaultCommands(cmd);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public String peekNextArg() {
+ return super.peekNextArg();
+ }
+
+ /**
+ * Return the {@link ShellCallback} for communicating back with the calling shell.
+ */
+ public ShellCallback getShellCallback() {
+ return mShellCallback;
+ }
+}
diff --git a/android-34/android/os/SimpleClock.java b/android-34/android/os/SimpleClock.java
new file mode 100644
index 0000000..efc271f
--- /dev/null
+++ b/android-34/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-34/android/os/SomeProvider.java b/android-34/android/os/SomeProvider.java
new file mode 100644
index 0000000..f5e247e
--- /dev/null
+++ b/android-34/android/os/SomeProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF 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.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import java.util.Arrays;
+
+public class SomeProvider extends ContentProvider {
+ private Cursor mCursor;
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return mCursor;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ final char[] valueRaw = new char[512];
+ Arrays.fill(valueRaw, '!');
+ final String value = new String(valueRaw);
+
+ final int count = values.getAsInteger(Intent.EXTRA_INDEX);
+ final MatrixCursor cursor = new MatrixCursor(new String[] { "_id", "value" });
+ for (int i = 0; i < count; i++) {
+ MatrixCursor.RowBuilder row = cursor.newRow();
+ row.add(0, i);
+ row.add(1, value);
+ }
+ mCursor = cursor;
+ return 1;
+ }
+}
diff --git a/android-34/android/os/SomeService.java b/android-34/android/os/SomeService.java
new file mode 100644
index 0000000..bdfaa44
--- /dev/null
+++ b/android-34/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-34/android/os/StatFs.java b/android-34/android/os/StatFs.java
new file mode 100644
index 0000000..bb6066e
--- /dev/null
+++ b/android-34/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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 #getAvailableBlocksLong()} 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-34/android/os/StatsServiceManager.java b/android-34/android/os/StatsServiceManager.java
new file mode 100644
index 0000000..de07e92
--- /dev/null
+++ b/android-34/android/os/StatsServiceManager.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SystemApi.Client;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the stats
+ * service.
+ *
+ * <p> Only the statsd mainline module will be able to access an instance of this class.
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class StatsServiceManager {
+ /**
+ * @hide
+ */
+ public StatsServiceManager() {}
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Get the system server binding object for StatsManagerService.
+ *
+ * <p> This blocks until the service instance is ready.
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @Nullable
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ */
+ @Nullable
+ private IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow()}
+ */
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor
+ *
+ * @param name the name of the binder service that cannot be found.
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "statscompanion" service.
+ */
+ @NonNull
+ public ServiceRegisterer getStatsCompanionServiceRegisterer() {
+ return new ServiceRegisterer("statscompanion");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "statsmanager" service.
+ */
+ @NonNull
+ public ServiceRegisterer getStatsManagerServiceRegisterer() {
+ return new ServiceRegisterer("statsmanager");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "statsd" service.
+ */
+ @NonNull
+ public ServiceRegisterer getStatsdServiceRegisterer() {
+ return new ServiceRegisterer("stats");
+ }
+}
diff --git a/android-34/android/os/StrictMode.java b/android-34/android/os/StrictMode.java
new file mode 100644
index 0000000..180735b
--- /dev/null
+++ b/android-34/android/os/StrictMode.java
@@ -0,0 +1,3202 @@
+/*
+ * 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 android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.IActivityManager;
+import android.app.IUnsafeIntentStrictModeCallback;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.UnsupportedAppUsage;
+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.content.res.Configuration;
+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.IncorrectContextUseViolation;
+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.UnsafeIntentLaunchViolation;
+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.util.SparseLongArray;
+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() {
+ * 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.
+ */
+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;
+
+ // Only log a dropbox entry at most every 30 seconds
+ private static final long MIN_DROPBOX_INTERVAL_MS = 3000;
+
+ // 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,
+ DETECT_VM_INCORRECT_CONTEXT_USE,
+ DETECT_VM_UNSAFE_INTENT_LAUNCH,
+ 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_INCORRECT_CONTEXT_USE = 1 << 12;
+ /** @hide */
+ private static final int DETECT_VM_UNSAFE_INTENT_LAUNCH = 1 << 13;
+
+ /** @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;
+
+ /**
+ * Detect explicit calls to {@link Runtime#gc()}.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long DETECT_EXPLICIT_GC = 3400644L;
+
+ // 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 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.
+ */
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ 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();
+ }
+ if (CompatChanges.isChangeEnabled(DETECT_EXPLICIT_GC)) {
+ detectExplicitGc();
+ }
+ 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 calls to {@link Runtime#gc()}.
+ */
+ public @NonNull Builder detectExplicitGc() {
+ return enable(DETECT_THREAD_EXPLICIT_GC);
+ }
+
+ /**
+ * Disable detection of calls to {@link Runtime#gc()}.
+ */
+ public @NonNull Builder permitExplicitGc() {
+ 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 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() {
+ synchronized (StrictMode.class) {
+ sExpectedActivityInstanceCount.clear();
+ }
+ 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.
+ */
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ 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();
+ }
+ if (targetSdk >= Build.VERSION_CODES.R) {
+ detectIncorrectContextUse();
+ }
+ if (targetSdk >= Build.VERSION_CODES.S) {
+ detectUnsafeIntentLaunch();
+ }
+
+ // 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 androidx.core.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#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);
+ }
+
+ /**
+ * Detect attempts to invoke a method on a {@link Context} that is not suited for such
+ * operation.
+ * <p>An example of this is trying to obtain an instance of UI service (e.g.
+ * {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
+ * allowed, since a non-visual {@link Context} is not adjusted to any visual area, and
+ * therefore can report incorrect metrics or resources.
+ * @see Context#getDisplay()
+ * @see Context#getSystemService(String)
+ */
+ public @NonNull Builder detectIncorrectContextUse() {
+ return enable(DETECT_VM_INCORRECT_CONTEXT_USE);
+ }
+
+ /**
+ * Disable detection of incorrect context use.
+ *
+ * @see #detectIncorrectContextUse()
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull Builder permitIncorrectContextUse() {
+ return disable(DETECT_VM_INCORRECT_CONTEXT_USE);
+ }
+
+ /**
+ * Detect when your app sends an unsafe {@link Intent}.
+ * <p>
+ * Violations may indicate security vulnerabilities in the design of
+ * your app, where a malicious app could trick you into granting
+ * {@link Uri} permissions or launching unexported components. Here
+ * are some typical design patterns that can be used to safely
+ * resolve these violations:
+ * <ul>
+ * <li> If you are sending an implicit intent to an unexported component, you should
+ * make it an explicit intent by using {@link Intent#setPackage},
+ * {@link Intent#setClassName} or {@link Intent#setComponent}.
+ * </li>
+ * <li> If you are unparceling and sending an intent from the intent delivered, The
+ * ideal approach is to migrate to using a {@link android.app.PendingIntent}, which
+ * ensures that your launch is performed using the identity of the original creator,
+ * completely avoiding the security issues described above.
+ * <li>If using a {@link android.app.PendingIntent} isn't feasible, an
+ * alternative approach is to create a brand new {@link Intent} and
+ * carefully copy only specific values from the original
+ * {@link Intent} after careful validation.
+ * </ul>
+ * <p>
+ * Note that this <em>may</em> detect false-positives if your app
+ * sends itself an {@link Intent} which is first routed through the
+ * OS, such as using {@link Intent#createChooser}. In these cases,
+ * careful inspection is required to determine if the return point
+ * into your app is appropriately protected with a signature
+ * permission or marked as unexported. If the return point is not
+ * protected, your app is likely vulnerable to malicious apps.
+ *
+ * @see Context#startActivity(Intent)
+ * @see Context#startService(Intent)
+ * @see Context#bindService(Intent, ServiceConnection, int)
+ * @see Context#sendBroadcast(Intent)
+ * @see android.app.Activity#setResult(int, Intent)
+ */
+ public @NonNull Builder detectUnsafeIntentLaunch() {
+ return enable(DETECT_VM_UNSAFE_INTENT_LAUNCH);
+ }
+
+ /**
+ * Permit your app to launch any {@link Intent} which originated
+ * from outside your app.
+ * <p>
+ * Disabling this check is <em>strongly discouraged</em>, as
+ * violations may indicate security vulnerabilities in the design of
+ * your app, where a malicious app could trick you into granting
+ * {@link Uri} permissions or launching unexported components.
+ *
+ * @see #detectUnsafeIntentLaunch()
+ */
+ public @NonNull Builder permitUnsafeIntentLaunch() {
+ return disable(DETECT_VM_UNSAFE_INTENT_LAUNCH);
+ }
+
+ /**
+ * 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.
+ /** Temporarily retained; appears to be missing UnsupportedAppUsage annotation */
+ private ArrayMap<Integer, Long> mLastViolationTime;
+ private SparseLongArray mRealLastViolationTime;
+
+ 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;
+ long now = SystemClock.uptimeMillis();
+ if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
+ if (mRealLastViolationTime != null) {
+ Long vtime = mRealLastViolationTime.get(crashFingerprint);
+ if (vtime != null) {
+ lastViolationTime = vtime;
+ }
+ clampViolationTimeMap(mRealLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS,
+ Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS)));
+ } else {
+ mRealLastViolationTime = new SparseLongArray(1);
+ }
+ mRealLastViolationTime.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)
+ && timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) {
+ 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 {
+
+ @Override
+ public void report(String message, Throwable allocationSite) {
+ onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
+ }
+
+ @Override
+ public void report(String message) {
+ onVmPolicyViolation(new LeakedClosableViolation(message));
+ }
+ }
+
+ /** 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 */
+ @UnsupportedAppUsage
+ @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);
+ }
+
+ if ((sVmPolicy.mask & DETECT_VM_UNSAFE_INTENT_LAUNCH) != 0) {
+ registerIntentMatchingRestrictionCallback();
+ }
+
+ setBlockGuardVmPolicy(sVmPolicy.mask);
+ }
+ }
+
+ private static void registerIntentMatchingRestrictionCallback() {
+ try {
+ ActivityManager.getService().registerStrictModeCallback(
+ new UnsafeIntentStrictModeCallback());
+ } catch (RemoteException e) {
+ /*
+ If exception is DeadObjectException it means system process is dead, so we can ignore
+ */
+ if (!(e instanceof DeadObjectException)) {
+ Log.e(TAG, "RemoteException handling StrictMode violation", e);
+ }
+ }
+ }
+
+ private static final class UnsafeIntentStrictModeCallback
+ extends IUnsafeIntentStrictModeCallback.Stub {
+ @Override
+ public void onImplicitIntentMatchedInternalComponent(Intent intent) {
+ if (StrictMode.vmUnsafeIntentLaunchEnabled()) {
+ StrictMode.onUnsafeIntentLaunch(intent,
+ "Launch of unsafe implicit intent: " + intent);
+ }
+ }
+ }
+
+ /** 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 boolean vmIncorrectContextUseEnabled() {
+ return (sVmPolicy.mask & DETECT_VM_INCORRECT_CONTEXT_USE) != 0;
+ }
+
+ /** @hide */
+ public static boolean vmUnsafeIntentLaunchEnabled() {
+ return (sVmPolicy.mask & DETECT_VM_UNSAFE_INTENT_LAUNCH) != 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();
+ final StringBuilder msg = new StringBuilder("Detected cleartext network traffic from UID ")
+ .append(uid);
+ if (rawAddr != null) {
+ try {
+ msg.append(" to ").append(InetAddress.getByAddress(rawAddr));
+ } catch (UnknownHostException ignored) {
+ }
+ }
+ msg.append(HexDump.dumpHexString(firstPacket).trim()).append(' ');
+ final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
+ onVmPolicyViolation(new CleartextNetworkViolation(msg.toString()), forceDeath);
+ }
+
+ /** @hide */
+ public static void onUntaggedSocket() {
+ onVmPolicyViolation(new UntaggedSocketViolation());
+ }
+
+ /** @hide */
+ public static void onImplicitDirectBoot() {
+ onVmPolicyViolation(new ImplicitDirectBootViolation());
+ }
+
+ /** @hide */
+ public static void onIncorrectContextUsed(String message, Throwable originStack) {
+ onVmPolicyViolation(new IncorrectContextUseViolation(message, originStack));
+ }
+
+ /**
+ * A helper method to verify if the {@code context} has a proper {@link Configuration} to obtain
+ * {@link android.view.LayoutInflater}, {@link android.view.ViewConfiguration} or
+ * {@link android.view.GestureDetector}. Throw {@link IncorrectContextUseViolation} if the
+ * {@code context} doesn't have a proper configuration.
+ * <p>
+ * Note that the context created via {@link Context#createConfigurationContext(Configuration)}
+ * is also regarded as a context with a proper configuration because the {@link Configuration}
+ * is handled by developers.
+ * </p>
+ * @param context The context to verify if it is a display associative context
+ * @param methodName The asserted method name
+ *
+ * @see Context#isConfigurationContext()
+ * @see Context#createConfigurationContext(Configuration)
+ * @see Context#getSystemService(String)
+ * @see Context#LAYOUT_INFLATER_SERVICE
+ * @see android.view.ViewConfiguration#get(Context)
+ * @see android.view.LayoutInflater#from(Context)
+ * @see IncorrectContextUseViolation
+ *
+ * @hide
+ */
+ public static void assertConfigurationContext(@NonNull Context context,
+ @NonNull String methodName) {
+ if (vmIncorrectContextUseEnabled() && !context.isConfigurationContext()) {
+ final String errorMessage = "Tried to access the API:" + methodName + " which needs to"
+ + " have proper configuration from a non-UI Context:" + context;
+ final String message = "The API:" + methodName + " needs a proper configuration."
+ + " Use UI contexts such as an activity or a context created"
+ + " via createWindowContext(Display, int, Bundle) or "
+ + " createConfigurationContext(Configuration) with a proper configuration.";
+ final Exception exception = new IllegalAccessException(errorMessage);
+ StrictMode.onIncorrectContextUsed(message, exception);
+ Log.e(TAG, errorMessage + " " + message, exception);
+ }
+ }
+
+ /**
+ * A helper method to verify if the {@code context} is a UI context and throw
+ * {@link IncorrectContextUseViolation} if the {@code context} is not a UI context.
+ *
+ * @param context The context to verify if it is a UI context
+ * @param methodName The asserted method name
+ *
+ * @see Context#isUiContext()
+ * @see IncorrectContextUseViolation
+ *
+ * @hide
+ */
+ public static void assertUiContext(@NonNull Context context, @NonNull String methodName) {
+ if (vmIncorrectContextUseEnabled() && !context.isUiContext()) {
+ final String errorMessage = "Tried to access UI related API:" + methodName
+ + " from a non-UI Context:" + context;
+ final String message = methodName + " should be accessed from Activity or other UI "
+ + "Contexts. Use an Activity or a Context created with "
+ + "Context#createWindowContext(int, Bundle), which are adjusted to "
+ + "the configuration and visual bounds of an area on screen.";
+ final Exception exception = new IllegalAccessException(errorMessage);
+ StrictMode.onIncorrectContextUsed(message, exception);
+ Log.e(TAG, errorMessage + " " + message, exception);
+ }
+ }
+
+ /** @hide */
+ public static void onUnsafeIntentLaunch(Intent intent) {
+ onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent));
+ }
+
+ /** @hide */
+ public static void onUnsafeIntentLaunch(Intent intent, String message) {
+ onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent, message));
+ }
+
+ /** 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<>();
+ private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray();
+
+ /**
+ * Clamp the given map by removing elements with timestamp older than the given retainSince.
+ */
+ private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime,
+ final long retainSince) {
+ for (int i = 0; i < violationTime.size(); ) {
+ if (violationTime.valueAt(i) < retainSince) {
+ // Remove stale entries
+ violationTime.removeAt(i);
+ } else {
+ i++;
+ }
+ }
+ // Ideally we'd cap the total size of the map, though it'll involve quickselect of topK,
+ // seems not worth it (saving some space immediately but they will be obsoleted soon anyway)
+ }
+
+ /** @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;
+ if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
+ synchronized (sRealLastVmViolationTime) {
+ if (sRealLastVmViolationTime.indexOfKey(fingerprint) >= 0) {
+ lastViolationTime = sRealLastVmViolationTime.get(fingerprint);
+ timeSinceLastViolationMillis = now - lastViolationTime;
+ }
+ if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
+ sRealLastVmViolationTime.put(fingerprint, now);
+ }
+ clampViolationTimeMap(sRealLastVmViolationTime,
+ now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS));
+ }
+ }
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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
+ * placeholder 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);
+ }
+
+ /** @hide */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void noteUntaggedSocket() {
+ if (vmUntaggedSocketEnabled()) onUntaggedSocket();
+ }
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static void incrementExpectedActivityCount(Class klass) {
+ if (klass == null) {
+ return;
+ }
+
+ synchronized (StrictMode.class) {
+ if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) {
+ return;
+ }
+
+ // Use the instance count from InstanceTracker as initial value.
+ Integer expected = sExpectedActivityInstanceCount.get(klass);
+ Integer newExpected =
+ expected == null ? InstanceTracker.getInstanceCount(klass) + 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. */
+ @UnsupportedAppUsage
+ 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(android.os.strictmode.Violation.class.getClassLoader(), android.os.strictmode.Violation.class);
+ 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-34/android/os/StrictModeTest.java b/android-34/android/os/StrictModeTest.java
new file mode 100644
index 0000000..60678e9
--- /dev/null
+++ b/android-34/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-34/android/os/SynchronousResultReceiver.java b/android-34/android/os/SynchronousResultReceiver.java
new file mode 100644
index 0000000..6e1d4b3
--- /dev/null
+++ b/android-34/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-34/android/os/SystemClock.java b/android-34/android/os/SystemClock.java
new file mode 100644
index 0000000..831ca86
--- /dev/null
+++ b/android-34/android/os/SystemClock.java
@@ -0,0 +1,384 @@
+/*
+ * 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.app.IAlarmManager;
+import android.app.time.UnixEpochTime;
+import android.app.timedetector.ITimeDetectorService;
+import android.compat.annotation.UnsupportedAppUsage;
+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";
+
+ private static volatile IAlarmManager sIAlarmManager;
+
+ /**
+ * 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 = getIAlarmManager();
+ if (mgr == null) {
+ Slog.e(TAG, "Unable to set RTC: 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;
+ }
+
+ private static IAlarmManager getIAlarmManager() {
+ if (sIAlarmManager == null) {
+ sIAlarmManager = IAlarmManager.Stub
+ .asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+ }
+ return sIAlarmManager;
+ }
+
+ /**
+ * 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();
+
+ /**
+ * Returns nanoseconds since boot, not counting time spent in deep sleep.
+ *
+ * @return nanoseconds of non-sleep uptime since boot.
+ * @hide
+ */
+ @CriticalNative
+ public static native long uptimeNanos();
+
+ /**
+ * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @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.
+ * <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.
+ * <p>
+ * Note that synchronization may occur using an insecure network protocol,
+ * so the returned time should not be used for security purposes.
+ * The device may resynchronize with the same or different network source
+ * at any time. Due to network delays, variations between servers, or local
+ * (client side) clock drift, the accuracy of the returned times cannot be
+ * guaranteed. In extreme cases, consecutive calls to {@link
+ * #currentNetworkTimeMillis()} could return times that are out of order.
+ *
+ * @throws DateTimeException when no network time can be provided.
+ * @hide
+ */
+ public static long currentNetworkTimeMillis() {
+ ITimeDetectorService timeDetectorService = ITimeDetectorService.Stub
+ .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
+ if (timeDetectorService != null) {
+ UnixEpochTime time;
+ try {
+ time = timeDetectorService.latestNetworkTime();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(DateTimeException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ if (time == null) {
+ // This is not expected.
+ throw new DateTimeException("Network based time is not available.");
+ }
+ long currentMillis = elapsedRealtime();
+ long deltaMs = currentMillis - time.getElapsedRealtimeMillis();
+ return time.getUnixEpochTimeMillis() + deltaMs;
+ } 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.
+ * <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.
+ * <p>
+ * Note that synchronization may occur using an insecure network protocol,
+ * so the returned time should not be used for security purposes.
+ * The device may resynchronize with the same or different network source
+ * at any time. Due to network delays, variations between servers, or local
+ * (client side) clock drift, the accuracy of the returned times cannot be
+ * guaranteed. In extreme cases, consecutive calls to {@link
+ * Clock#millis()} on the returned {@link Clock}could return times that are
+ * out of order.
+ *
+ * @throws DateTimeException when no network time can be provided.
+ */
+ 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) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (time == null) {
+ throw new DateTimeException("Gnss based time is not available.");
+ }
+ long currentNanos = elapsedRealtimeNanos();
+ long deltaMs = (currentNanos - time.getElapsedRealtimeNanos()) / 1000000L;
+ return time.getUnixEpochTimeMillis() + deltaMs;
+ }
+ };
+ }
+}
diff --git a/android-34/android/os/SystemConfigManager.java b/android-34/android/os/SystemConfigManager.java
new file mode 100644
index 0000000..77843d9
--- /dev/null
+++ b/android-34/android/os/SystemConfigManager.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SystemApi;
+import android.annotation.SystemService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Allows apps outside the system process to access various bits of configuration defined in
+ * /etc/sysconfig and its counterparts on OEM and vendor partitions.
+ *
+ * TODO: Intended for access by system mainline modules only. Marking as SystemApi until the
+ * module-only API surface is available.
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.SYSTEM_CONFIG_SERVICE)
+public class SystemConfigManager {
+ private static final String TAG = SystemConfigManager.class.getSimpleName();
+
+ private final ISystemConfig mInterface;
+
+ /** @hide **/
+ public SystemConfigManager() {
+ mInterface = ISystemConfig.Stub.asInterface(
+ ServiceManager.getService(Context.SYSTEM_CONFIG_SERVICE));
+ }
+
+ /**
+ * Returns a set of package names for carrier apps that are preinstalled on the device but
+ * should be disabled until the matching carrier's SIM is inserted into the device.
+ * @return A set of package names.
+ */
+ @RequiresPermission(Manifest.permission.READ_CARRIER_APP_INFO)
+ public @NonNull Set<String> getDisabledUntilUsedPreinstalledCarrierApps() {
+ try {
+ List<String> apps = mInterface.getDisabledUntilUsedPreinstalledCarrierApps();
+ return new ArraySet<>(apps);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught remote exception");
+ return Collections.emptySet();
+ }
+ }
+
+ /**
+ * Returns a map that describes helper apps associated with carrier apps that, like the apps
+ * returned by {@link #getDisabledUntilUsedPreinstalledCarrierApps()}, should be disabled until
+ * the correct SIM is inserted into the device.
+ * @return A map with keys corresponding to package names returned by
+ * {@link #getDisabledUntilUsedPreinstalledCarrierApps()} and values as lists of package
+ * names of helper apps.
+ */
+ @RequiresPermission(Manifest.permission.READ_CARRIER_APP_INFO)
+ public @NonNull Map<String, List<String>>
+ getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
+ try {
+ return (Map<String, List<String>>)
+ mInterface.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught remote exception");
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Returns a map that describes helper apps associated with carrier apps that, like the apps
+ * returned by {@link #getDisabledUntilUsedPreinstalledCarrierApps()}, should be disabled until
+ * the correct SIM is inserted into the device.
+ *
+ * <p>TODO(b/159069037) expose this and get rid of the other method that omits SDK version.
+ *
+ * @return A map with keys corresponding to package names returned by
+ * {@link #getDisabledUntilUsedPreinstalledCarrierApps()} and values as lists of package
+ * names of helper apps and the SDK versions when they were first added.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.READ_CARRIER_APP_INFO)
+ public @NonNull Map<String, List<CarrierAssociatedAppEntry>>
+ getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries() {
+ try {
+ return (Map<String, List<CarrierAssociatedAppEntry>>)
+ mInterface.getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught remote exception", e);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Get uids which have been granted given permission in system configuration.
+ *
+ * The uids and assigning permissions are defined on data/etc/platform.xml
+ *
+ * @param permissionName The target permission.
+ * @return The uids have been granted given permission in system configuration.
+ */
+ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ @NonNull
+ public int[] getSystemPermissionUids(@NonNull String permissionName) {
+ try {
+ return mInterface.getSystemPermissionUids(permissionName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get enabled component for a specific package
+ *
+ * @param packageName The target package.
+ * @return The enabled component
+ * {@hide}
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public List<ComponentName> getEnabledComponentOverrides(@NonNull String packageName) {
+ try {
+ return mInterface.getEnabledComponentOverrides(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the components that are enabled by default as VR mode listener services.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)
+ public List<ComponentName> getDefaultVrComponents() {
+ try {
+ return mInterface.getDefaultVrComponents();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return Collections.emptyList();
+ }
+}
diff --git a/android-34/android/os/SystemProperties.java b/android-34/android/os/SystemProperties.java
new file mode 100644
index 0000000..aa283a2
--- /dev/null
+++ b/android-34/android/os/SystemProperties.java
@@ -0,0 +1,385 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.internal.annotations.GuardedBy;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+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.
+ *
+ * <p>Use this class only for the system properties that are local. e.g., within
+ * an app, a partition, or a module. For system properties used across the
+ * boundaries, formally define them in <code>*.sysprop</code> files and use the
+ * auto-generated methods. For more information, see <a href=
+ * "https://source.android.com/devices/architecture/sysprops-apis">Implementing
+ * System Properties as APIs</a>.</p>
+ *
+ * {@hide}
+ */
+@SystemApi
+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(trackingBug = 172649311)
+ 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());
+ }
+ }
+ }
+ }
+
+ // The one-argument version of native_get used to be a regular native function. Nowadays,
+ // we use the two-argument form of native_get all the time, but we can't just delete the
+ // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
+ // indicates. Let's just live with having a Java function with a very unusual name.
+ @UnsupportedAppUsage
+ private static String native_get(String key) {
+ return native_get(key, "");
+ }
+
+ @FastNative
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static native String native_get(String key, String def);
+ @FastNative
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static native int native_get_int(String key, int def);
+ @FastNative
+ @UnsupportedAppUsage
+ private static native long native_get_long(String key, long def);
+ @FastNative
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static native boolean native_get_boolean(String key, boolean def);
+
+ @FastNative
+ private static native long native_find(String name);
+ @FastNative
+ private static native String native_get(long handle);
+ @CriticalNative
+ private static native int native_get_int(long handle, int def);
+ @CriticalNative
+ private static native long native_get_long(long handle, long def);
+ @CriticalNative
+ private static native boolean native_get_boolean(long handle, boolean def);
+
+ // _NOT_ FastNative: native_set performs IPC and can block
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static native void native_set(String key, String def);
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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
+ 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
+ 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
+ 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 for non read-only properties if the {@code val} exceeds
+ * 91 characters
+ * @throws RuntimeException if the property cannot be set, for example, if it was blocked by
+ * SELinux. libc will log the underlying reason.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static void set(@NonNull String key, @Nullable String val) {
+ if (val != null && !key.startsWith("ro.") && val.getBytes(StandardCharsets.UTF_8).length
+ > PROP_VALUE_MAX) {
+ throw new IllegalArgumentException("value of system property '" + key
+ + "' is longer than " + PROP_VALUE_MAX + " bytes: " + 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);
+ }
+ }
+
+ /**
+ * Remove the target callback.
+ *
+ * @param callback The {@link Runnable} that should be removed.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static void removeChangeCallback(@NonNull Runnable callback) {
+ synchronized (sChangeCallbacks) {
+ if (sChangeCallbacks.contains(callback)) {
+ sChangeCallbacks.remove(callback);
+ }
+ }
+ }
+
+ @SuppressWarnings("unused") // Called from native code.
+ private static void callChangeCallbacks() {
+ ArrayList<Runnable> callbacks = null;
+ synchronized (sChangeCallbacks) {
+ //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!");
+ if (sChangeCallbacks.size() == 0) {
+ return;
+ }
+ 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) {
+ // Ignore and try to go on. Don't use wtf here: that
+ // will cause the process to exit on some builds and break tests.
+ Log.e(TAG, "Exception in SystemProperties change callback", t);
+ }
+ }
+ } 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() {
+ }
+
+ /**
+ * Look up a property location by name.
+ * @name name of the property
+ * @return property handle or {@code null} if property isn't set
+ * @hide
+ */
+ @Nullable public static Handle find(@NonNull String name) {
+ long nativeHandle = native_find(name);
+ if (nativeHandle == 0) {
+ return null;
+ }
+ return new Handle(nativeHandle);
+ }
+
+ /**
+ * Handle to a pre-located property. Looking up a property handle in advance allows
+ * for optimal repeated lookup of a single property.
+ * @hide
+ */
+ public static final class Handle {
+
+ private final long mNativeHandle;
+
+ /**
+ * @return Value of the property
+ */
+ @NonNull public String get() {
+ return native_get(mNativeHandle);
+ }
+ /**
+ * @param def default value
+ * @return value or {@code def} on parse error
+ */
+ public int getInt(int def) {
+ return native_get_int(mNativeHandle, def);
+ }
+ /**
+ * @param def default value
+ * @return value or {@code def} on parse error
+ */
+ public long getLong(long def) {
+ return native_get_long(mNativeHandle, def);
+ }
+ /**
+ * @param def default value
+ * @return value or {@code def} on parse error
+ */
+ public boolean getBoolean(boolean def) {
+ return native_get_boolean(mNativeHandle, def);
+ }
+
+ private Handle(long nativeHandle) {
+ mNativeHandle = nativeHandle;
+ }
+ }
+}
diff --git a/android-34/android/os/SystemService.java b/android-34/android/os/SystemService.java
new file mode 100644
index 0000000..9b0ac8f
--- /dev/null
+++ b/android-34/android/os/SystemService.java
@@ -0,0 +1,151 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+
+import com.google.android.collect.Maps;
+
+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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/SystemUpdateManager.java b/android-34/android/os/SystemUpdateManager.java
new file mode 100644
index 0000000..9146731
--- /dev/null
+++ b/android-34/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-34/android/os/SystemVibrator.java b/android-34/android/os/SystemVibrator.java
new file mode 100644
index 0000000..bf72b1d
--- /dev/null
+++ b/android-34/android/os/SystemVibrator.java
@@ -0,0 +1,681 @@
+/*
+ * 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.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.hardware.vibrator.IVibrator;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Range;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+/**
+ * Vibrator implementation that controls the main system vibrator.
+ *
+ * @hide
+ */
+public class SystemVibrator extends Vibrator {
+ private static final String TAG = "Vibrator";
+
+ private final VibratorManager mVibratorManager;
+ private final Context mContext;
+
+ @GuardedBy("mBrokenListeners")
+ private final ArrayList<MultiVibratorStateListener> mBrokenListeners = new ArrayList<>();
+
+ @GuardedBy("mRegisteredListeners")
+ private final ArrayMap<OnVibratorStateChangedListener, MultiVibratorStateListener>
+ mRegisteredListeners = new ArrayMap<>();
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private VibratorInfo mVibratorInfo;
+
+ @UnsupportedAppUsage
+ public SystemVibrator(Context context) {
+ super(context);
+ mContext = context;
+ mVibratorManager = mContext.getSystemService(VibratorManager.class);
+ }
+
+ @Override
+ protected VibratorInfo getInfo() {
+ synchronized (mLock) {
+ if (mVibratorInfo != null) {
+ return mVibratorInfo;
+ }
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager.");
+ return VibratorInfo.EMPTY_VIBRATOR_INFO;
+ }
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ // It is known that the device has no vibrator, so cache and return info that
+ // reflects the lack of support for effects/primitives.
+ return mVibratorInfo = new NoVibratorInfo();
+ }
+ VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
+ for (int i = 0; i < vibratorIds.length; i++) {
+ Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]);
+ if (vibrator instanceof NullVibrator) {
+ Log.w(TAG, "Vibrator manager service not ready; "
+ + "Info not yet available for vibrator: " + vibratorIds[i]);
+ // This should never happen after the vibrator manager service is ready.
+ // Skip caching this vibrator until then.
+ return VibratorInfo.EMPTY_VIBRATOR_INFO;
+ }
+ vibratorInfos[i] = vibrator.getInfo();
+ }
+ if (vibratorInfos.length == 1) {
+ // Device has a single vibrator info, cache and return successfully loaded info.
+ return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]);
+ }
+ // Device has multiple vibrators, generate a single info representing all of them.
+ return mVibratorInfo = new MultiVibratorInfo(vibratorInfos);
+ }
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
+ return false;
+ }
+ return mVibratorManager.getVibratorIds().length > 0;
+ }
+
+ @Override
+ public boolean isVibrating() {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager.");
+ return false;
+ }
+ for (int vibratorId : mVibratorManager.getVibratorIds()) {
+ if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mContext == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+ return;
+ }
+ addVibratorStateListener(mContext.getMainExecutor(), listener);
+ }
+
+ @Override
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ Objects.requireNonNull(executor);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
+ return;
+ }
+ MultiVibratorStateListener delegate = null;
+ try {
+ synchronized (mRegisteredListeners) {
+ // If listener is already registered, reject and return.
+ if (mRegisteredListeners.containsKey(listener)) {
+ Log.w(TAG, "Listener already registered.");
+ return;
+ }
+ delegate = new MultiVibratorStateListener(executor, listener);
+ delegate.register(mVibratorManager);
+ mRegisteredListeners.put(listener, delegate);
+ delegate = null;
+ }
+ } finally {
+ if (delegate != null && delegate.hasRegisteredListeners()) {
+ // The delegate listener was left in a partial state with listeners registered to
+ // some but not all vibrators. Keep track of this to try to unregister them later.
+ synchronized (mBrokenListeners) {
+ mBrokenListeners.add(delegate);
+ }
+ }
+ tryUnregisterBrokenListeners();
+ }
+ }
+
+ @Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
+ return;
+ }
+ synchronized (mRegisteredListeners) {
+ if (mRegisteredListeners.containsKey(listener)) {
+ MultiVibratorStateListener delegate = mRegisteredListeners.get(listener);
+ delegate.unregister(mVibratorManager);
+ mRegisteredListeners.remove(listener);
+ }
+ }
+ tryUnregisterBrokenListeners();
+ }
+
+ @Override
+ public boolean hasAmplitudeControl() {
+ return getInfo().hasAmplitudeControl();
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ VibrationAttributes attrs) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
+ return false;
+ }
+ CombinedVibration combinedEffect = CombinedVibration.createParallel(effect);
+ return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attrs);
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
+ String reason, @NonNull VibrationAttributes attributes) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager.");
+ return;
+ }
+ CombinedVibration combinedEffect = CombinedVibration.createParallel(effect);
+ mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
+ }
+
+ @Override
+ public void cancel() {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
+ return;
+ }
+ mVibratorManager.cancel();
+ }
+
+ @Override
+ public void cancel(int usageFilter) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
+ return;
+ }
+ mVibratorManager.cancel(usageFilter);
+ }
+
+ /**
+ * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
+ * that were left registered to vibrators after failures to register them to all vibrators.
+ *
+ * <p>This might happen if {@link MultiVibratorStateListener} fails to register to any vibrator
+ * and also fails to unregister any previously registered single listeners to other vibrators.
+ *
+ * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
+ * fail silently and attempt to unregister the same broken listener later.
+ */
+ private void tryUnregisterBrokenListeners() {
+ synchronized (mBrokenListeners) {
+ try {
+ for (int i = mBrokenListeners.size(); --i >= 0; ) {
+ mBrokenListeners.get(i).unregister(mVibratorManager);
+ mBrokenListeners.remove(i);
+ }
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Failed to unregister broken listener", e);
+ }
+ }
+ }
+
+ /** Listener for a single vibrator state change. */
+ private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
+ private final MultiVibratorStateListener mAllVibratorsListener;
+ private final int mVibratorIdx;
+
+ SingleVibratorStateListener(MultiVibratorStateListener listener, int vibratorIdx) {
+ mAllVibratorsListener = listener;
+ mVibratorIdx = vibratorIdx;
+ }
+
+ @Override
+ public void onVibratorStateChanged(boolean isVibrating) {
+ mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
+ }
+ }
+
+ /**
+ * Represents a device with no vibrator as a single {@link VibratorInfo}.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class NoVibratorInfo extends VibratorInfo {
+ public NoVibratorInfo() {
+ // Use empty arrays to indicate no support, while null would indicate support unknown.
+ super(/* id= */ -1,
+ /* capabilities= */ 0,
+ /* supportedEffects= */ new SparseBooleanArray(),
+ /* supportedBraking= */ new SparseBooleanArray(),
+ /* supportedPrimitives= */ new SparseIntArray(),
+ /* primitiveDelayMax= */ 0,
+ /* compositionSizeMax= */ 0,
+ /* pwlePrimitiveDurationMax= */ 0,
+ /* pwleSizeMax= */ 0,
+ /* qFactor= */ Float.NaN,
+ new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
+ /* minFrequencyHz= */ Float.NaN,
+ /* frequencyResolutionHz= */ Float.NaN,
+ /* maxAmplitudes= */ null));
+ }
+ }
+
+ /**
+ * Represents multiple vibrator information as a single {@link VibratorInfo}.
+ *
+ * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
+ * support.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class MultiVibratorInfo extends VibratorInfo {
+ // Epsilon used for float comparison applied in calculations for the merged info.
+ private static final float EPSILON = 1e-5f;
+
+ public MultiVibratorInfo(VibratorInfo[] vibrators) {
+ // Need to use an extra constructor to share the computation in super initialization.
+ this(vibrators, frequencyProfileIntersection(vibrators));
+ }
+
+ private MultiVibratorInfo(VibratorInfo[] vibrators,
+ VibratorInfo.FrequencyProfile mergedProfile) {
+ super(/* id= */ -1,
+ capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
+ supportedEffectsIntersection(vibrators),
+ supportedBrakingIntersection(vibrators),
+ supportedPrimitivesAndDurationsIntersection(vibrators),
+ integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
+ floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+ mergedProfile);
+ }
+
+ private static int capabilitiesIntersection(VibratorInfo[] infos,
+ boolean frequencyProfileIsEmpty) {
+ int intersection = ~0;
+ for (VibratorInfo info : infos) {
+ intersection &= info.getCapabilities();
+ }
+ if (frequencyProfileIsEmpty) {
+ // Revoke frequency control if the merged frequency profile ended up empty.
+ intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+ }
+ return intersection;
+ }
+
+ @Nullable
+ private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isBrakingSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
+
+ brakingIdLoop:
+ for (int i = 0; i < firstVibratorBraking.size(); i++) {
+ int brakingId = firstVibratorBraking.keyAt(i);
+ if (!firstVibratorBraking.valueAt(i)) {
+ // The first vibrator already doesn't support this braking, so skip it.
+ continue brakingIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (!infos[j].hasBrakingSupport(brakingId)) {
+ // One vibrator doesn't support this braking, so the intersection doesn't.
+ continue brakingIdLoop;
+ }
+ }
+
+ intersection.put(brakingId, true);
+ }
+
+ return intersection;
+ }
+
+ @Nullable
+ private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isEffectSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
+
+ effectIdLoop:
+ for (int i = 0; i < firstVibratorEffects.size(); i++) {
+ int effectId = firstVibratorEffects.keyAt(i);
+ if (!firstVibratorEffects.valueAt(i)) {
+ // The first vibrator already doesn't support this effect, so skip it.
+ continue effectIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
+ // One vibrator doesn't support this effect, so the intersection doesn't.
+ continue effectIdLoop;
+ }
+ }
+
+ intersection.put(effectId, true);
+ }
+
+ return intersection;
+ }
+
+ @NonNull
+ private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
+ VibratorInfo[] infos) {
+ SparseIntArray intersection = new SparseIntArray();
+ SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
+
+ primitiveIdLoop:
+ for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
+ int primitiveId = firstVibratorPrimitives.keyAt(i);
+ int primitiveDuration = firstVibratorPrimitives.valueAt(i);
+ if (primitiveDuration == 0) {
+ // The first vibrator already doesn't support this primitive, so skip it.
+ continue primitiveIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
+ if (vibratorPrimitiveDuration == 0) {
+ // One vibrator doesn't support this primitive, so the intersection doesn't.
+ continue primitiveIdLoop;
+ } else {
+ // The primitive vibration duration is the maximum among all vibrators.
+ primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
+ }
+ }
+
+ intersection.put(primitiveId, primitiveDuration);
+ }
+ return intersection;
+ }
+
+ private static int integerLimitIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Integer> propertyGetter) {
+ int limit = 0; // Limit 0 means unlimited
+ for (VibratorInfo info : infos) {
+ int vibratorLimit = propertyGetter.apply(info);
+ if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
+ // This vibrator is limited and intersection is unlimited or has a larger limit:
+ // use smaller limit here for the intersection.
+ limit = vibratorLimit;
+ }
+ }
+ return limit;
+ }
+
+ private static float floatPropertyIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Float> propertyGetter) {
+ float property = propertyGetter.apply(infos[0]);
+ if (Float.isNaN(property)) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return Float.NaN;
+ }
+ for (int i = 1; i < infos.length; i++) {
+ if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
+ // If one vibrator has a different value then the intersection is undefined.
+ return Float.NaN;
+ }
+ }
+ return property;
+ }
+
+ @NonNull
+ private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+ float freqResolution = floatPropertyIntersection(infos,
+ info -> info.getFrequencyProfile().getFrequencyResolutionHz());
+ float resonantFreq = floatPropertyIntersection(infos,
+ VibratorInfo::getResonantFrequencyHz);
+ Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
+
+ if ((freqRange == null) || Float.isNaN(freqResolution)) {
+ return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
+ }
+
+ int amplitudeCount =
+ Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
+ float[] maxAmplitudes = new float[amplitudeCount];
+
+ // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
+ // will fail if the loop below is broken and do not replace filled values with actual
+ // vibrator measurements.
+ Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
+
+ for (VibratorInfo info : infos) {
+ Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
+ float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
+ int vibratorStartIdx = Math.round(
+ (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
+ int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
+
+ if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
+ Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+ + " profiles: attempted to fetch from vibrator "
+ + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
+ return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
+ }
+
+ for (int i = 0; i < maxAmplitudes.length; i++) {
+ maxAmplitudes[i] = Math.min(maxAmplitudes[i],
+ vibratorMaxAmplitudes[vibratorStartIdx + i]);
+ }
+ }
+
+ return new FrequencyProfile(resonantFreq, freqRange.getLower(),
+ freqResolution, maxAmplitudes);
+ }
+
+ @Nullable
+ private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
+ float frequencyResolution) {
+ Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
+ if (firstRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+ float intersectionLower = firstRange.getLower();
+ float intersectionUpper = firstRange.getUpper();
+
+ // Generate the intersection of all vibrator supported ranges, making sure that both
+ // min supported frequencies are aligned w.r.t. the frequency resolution.
+
+ for (int i = 1; i < infos.length; i++) {
+ Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
+ if (vibratorRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+
+ if ((vibratorRange.getLower() >= intersectionUpper)
+ || (vibratorRange.getUpper() <= intersectionLower)) {
+ // If the range and intersection are disjoint then the intersection is undefined
+ return null;
+ }
+
+ float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
+ if ((frequencyDelta % frequencyResolution) > EPSILON) {
+ // If the intersection is not aligned with one vibrator then it's undefined
+ return null;
+ }
+
+ intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
+ intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
+ }
+
+ if ((intersectionUpper - intersectionLower) < frequencyResolution) {
+ // If the intersection is empty then it's undefined.
+ return null;
+ }
+
+ return Range.create(intersectionLower, intersectionUpper);
+ }
+ }
+
+ /**
+ * Listener for all vibrators state change.
+ *
+ * <p>This registers a listener to all vibrators to merge the callbacks into a single state
+ * that is set to true if any individual vibrator is also true, and false otherwise.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class MultiVibratorStateListener {
+ private final Object mLock = new Object();
+ private final Executor mExecutor;
+ private final OnVibratorStateChangedListener mDelegate;
+
+ @GuardedBy("mLock")
+ private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
+ new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private int mInitializedMask;
+ @GuardedBy("mLock")
+ private int mVibratingMask;
+
+ public MultiVibratorStateListener(@NonNull Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ mExecutor = executor;
+ mDelegate = listener;
+ }
+
+ /** Returns true if at least one listener was registered to an individual vibrator. */
+ public boolean hasRegisteredListeners() {
+ synchronized (mLock) {
+ return mVibratorListeners.size() > 0;
+ }
+ }
+
+ /** Registers a listener to all individual vibrators in {@link VibratorManager}. */
+ public void register(VibratorManager vibratorManager) {
+ int[] vibratorIds = vibratorManager.getVibratorIds();
+ synchronized (mLock) {
+ for (int i = 0; i < vibratorIds.length; i++) {
+ int vibratorId = vibratorIds[i];
+ SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
+ try {
+ vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
+ listener);
+ mVibratorListeners.put(vibratorId, listener);
+ } catch (RuntimeException e) {
+ try {
+ unregister(vibratorManager);
+ } catch (RuntimeException e1) {
+ Log.w(TAG,
+ "Failed to unregister listener while recovering from a failed "
+ + "register call", e1);
+ }
+ throw e;
+ }
+ }
+ }
+ }
+
+ /** Unregisters the listeners from all individual vibrators in {@link VibratorManager}. */
+ public void unregister(VibratorManager vibratorManager) {
+ synchronized (mLock) {
+ for (int i = mVibratorListeners.size(); --i >= 0; ) {
+ int vibratorId = mVibratorListeners.keyAt(i);
+ SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
+ vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
+ mVibratorListeners.removeAt(i);
+ }
+ }
+ }
+
+ /** Callback triggered by {@link SingleVibratorStateListener} for each vibrator. */
+ public void onVibrating(int vibratorIdx, boolean vibrating) {
+ mExecutor.execute(() -> {
+ boolean shouldNotifyStateChange;
+ boolean isAnyVibrating;
+ synchronized (mLock) {
+ // Bitmask indicating that all vibrators have been initialized.
+ int allInitializedMask = (1 << mVibratorListeners.size()) - 1;
+
+ // Save current global state before processing this vibrator state change.
+ boolean previousIsAnyVibrating = (mVibratingMask != 0);
+ boolean previousAreAllInitialized = (mInitializedMask == allInitializedMask);
+
+ // Mark this vibrator as initialized.
+ int vibratorMask = (1 << vibratorIdx);
+ mInitializedMask |= vibratorMask;
+
+ // Flip the vibrating bit flag for this vibrator, only if the state is changing.
+ boolean previousVibrating = (mVibratingMask & vibratorMask) != 0;
+ if (previousVibrating != vibrating) {
+ mVibratingMask ^= vibratorMask;
+ }
+
+ // Check new global state after processing this vibrator state change.
+ isAnyVibrating = (mVibratingMask != 0);
+ boolean areAllInitialized = (mInitializedMask == allInitializedMask);
+
+ // Prevent multiple triggers with the same state.
+ // Trigger once when all vibrators have reported their state, and then only when
+ // the merged vibrating state changes.
+ boolean isStateChanging = (previousIsAnyVibrating != isAnyVibrating);
+ shouldNotifyStateChange =
+ areAllInitialized && (!previousAreAllInitialized || isStateChanging);
+ }
+ // Notify delegate listener outside the lock, only if merged state is changing.
+ if (shouldNotifyStateChange) {
+ mDelegate.onVibratorStateChanged(isAnyVibrating);
+ }
+ });
+ }
+ }
+}
diff --git a/android-34/android/os/SystemVibratorManager.java b/android-34/android/os/SystemVibratorManager.java
new file mode 100644
index 0000000..eb2a712
--- /dev/null
+++ b/android-34/android/os/SystemVibratorManager.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * VibratorManager implementation that controls the system vibrators.
+ *
+ * @hide
+ */
+public class SystemVibratorManager extends VibratorManager {
+ private static final String TAG = "VibratorManager";
+
+ private final IVibratorManagerService mService;
+ private final Context mContext;
+ private final Binder mToken = new Binder();
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private int[] mVibratorIds;
+ @GuardedBy("mLock")
+ private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
+ OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ public SystemVibratorManager(Context context) {
+ super(context);
+ mContext = context;
+ mService = IVibratorManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
+ }
+
+ @NonNull
+ @Override
+ public int[] getVibratorIds() {
+ synchronized (mLock) {
+ if (mVibratorIds != null) {
+ return mVibratorIds;
+ }
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
+ } else {
+ return mVibratorIds = mService.getVibratorIds();
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return new int[0];
+ }
+ }
+
+ @NonNull
+ @Override
+ public Vibrator getVibrator(int vibratorId) {
+ synchronized (mLock) {
+ Vibrator vibrator = mVibrators.get(vibratorId);
+ if (vibrator != null) {
+ return vibrator;
+ }
+ VibratorInfo info = null;
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
+ } else {
+ info = mService.getVibratorInfo(vibratorId);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ if (info != null) {
+ vibrator = new SingleVibrator(info);
+ mVibrators.put(vibratorId, vibrator);
+ } else {
+ vibrator = NullVibrator.getInstance();
+ }
+ return vibrator;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Vibrator getDefaultVibrator() {
+ return mContext.getSystemService(Vibrator.class);
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable CombinedVibration effect, @Nullable VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
+ return false;
+ }
+ try {
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect.", e);
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull CombinedVibration effect,
+ String reason, @Nullable VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.vibrate(uid, mContext.getAssociatedDisplayId(), opPkg, effect, attributes,
+ reason, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
+ }
+
+ @Override
+ public void cancel(int usageFilter) {
+ cancelVibration(usageFilter);
+ }
+
+ private void cancelVibration(int usageFilter) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.cancelVibrate(usageFilter, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration.", e);
+ }
+ }
+
+ /** Listener for vibrations on a single vibrator. */
+ private static class OnVibratorStateChangedListenerDelegate extends
+ IVibratorStateListener.Stub {
+ private final Executor mExecutor;
+ private final Vibrator.OnVibratorStateChangedListener mListener;
+
+ OnVibratorStateChangedListenerDelegate(
+ @NonNull Vibrator.OnVibratorStateChangedListener listener,
+ @NonNull Executor executor) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onVibrating(boolean isVibrating) {
+ mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+ }
+ }
+
+ /** Controls vibrations on a single vibrator. */
+ private final class SingleVibrator extends Vibrator {
+ private final VibratorInfo mVibratorInfo;
+
+ SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
+ mVibratorInfo = vibratorInfo;
+ }
+
+ @Override
+ protected VibratorInfo getInfo() {
+ return mVibratorInfo;
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public boolean hasAmplitudeControl() {
+ return mVibratorInfo.hasAmplitudeControl();
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable VibrationAttributes attrs) {
+ CombinedVibration combined = CombinedVibration.startParallel()
+ .addVibrator(mVibratorInfo.getId(), effect)
+ .combine();
+ return SystemVibratorManager.this.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined,
+ attrs);
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+ @NonNull VibrationAttributes attributes) {
+ CombinedVibration combined = CombinedVibration.startParallel()
+ .addVibrator(mVibratorInfo.getId(), vibe)
+ .combine();
+ SystemVibratorManager.this.vibrate(uid, opPkg, combined, reason, attributes);
+ }
+
+ @Override
+ public void cancel() {
+ SystemVibratorManager.this.cancel();
+ }
+
+ @Override
+ public void cancel(int usageFilter) {
+ SystemVibratorManager.this.cancel(usageFilter);
+ }
+
+ @Override
+ public boolean isVibrating() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
+ + "; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.isVibrating(mVibratorInfo.getId());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ @Override
+ public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mContext == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+ return;
+ }
+ addVibratorStateListener(mContext.getMainExecutor(), listener);
+ }
+
+ @Override
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ Objects.requireNonNull(executor);
+ if (mService == null) {
+ Log.w(TAG,
+ "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
+ + "; no vibrator service.");
+ return;
+ }
+ synchronized (mLock) {
+ // If listener is already registered, reject and return.
+ if (mListeners.containsKey(listener)) {
+ Log.w(TAG, "Listener already registered.");
+ return;
+ }
+ try {
+ OnVibratorStateChangedListenerDelegate delegate =
+ new OnVibratorStateChangedListenerDelegate(listener, executor);
+ if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
+ Log.w(TAG, "Failed to add vibrate state listener to vibrator "
+ + mVibratorInfo.getId());
+ return;
+ }
+ mListeners.put(listener, delegate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mService == null) {
+ Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+ + mVibratorInfo.getId() + "; no vibrator service.");
+ return;
+ }
+ synchronized (mLock) {
+ // Check if the listener is registered, otherwise will return.
+ if (mListeners.containsKey(listener)) {
+ OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
+ try {
+ if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
+ delegate)) {
+ Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+ + mVibratorInfo.getId());
+ return;
+ }
+ mListeners.remove(listener);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/android-34/android/os/TelephonyServiceManager.java b/android-34/android/os/TelephonyServiceManager.java
new file mode 100644
index 0000000..6993671
--- /dev/null
+++ b/android-34/android/os/TelephonyServiceManager.java
@@ -0,0 +1,197 @@
+/*
+ * 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.Nullable;
+import android.content.Context;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the telephony
+ * service.
+ *
+ * <p>Only the telephony mainline module will be able to access an instance of this class.
+ *
+ * @hide
+ */
+public class TelephonyServiceManager {
+ /**
+ * @hide
+ */
+ public TelephonyServiceManager() {
+ }
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public static final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Register a system server binding object for a service.
+ */
+ public void register(@NonNull IBinder service) {
+ ServiceManager.addService(mServiceName, service);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @NonNull
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ */
+ @Nullable
+ public IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow}.
+ *
+ * @hide
+ */
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor.
+ *
+ * @param name the name of the binder service that cannot be found.
+ *
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "telephony" service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyServiceRegisterer() {
+ return new ServiceRegisterer(Context.TELEPHONY_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the telephony IMS service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyImsServiceRegisterer() {
+ return new ServiceRegisterer(Context.TELEPHONY_IMS_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the telephony RCS message service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyRcsMessageServiceRegisterer() {
+ return new ServiceRegisterer(Context.TELEPHONY_RCS_MESSAGE_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the subscription service.
+ */
+ @NonNull
+ public ServiceRegisterer getSubscriptionServiceRegisterer() {
+ return new ServiceRegisterer("isub");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the phone sub service.
+ */
+ @NonNull
+ public ServiceRegisterer getPhoneSubServiceRegisterer() {
+ return new ServiceRegisterer("iphonesubinfo");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the opportunistic network service.
+ */
+ @NonNull
+ public ServiceRegisterer getOpportunisticNetworkServiceRegisterer() {
+ return new ServiceRegisterer("ions");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the carrier config service.
+ */
+ @NonNull
+ public ServiceRegisterer getCarrierConfigServiceRegisterer() {
+ return new ServiceRegisterer(Context.CARRIER_CONFIG_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "SMS" service.
+ */
+ @NonNull
+ public ServiceRegisterer getSmsServiceRegisterer() {
+ return new ServiceRegisterer("isms");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the eUICC controller service.
+ */
+ @NonNull
+ public ServiceRegisterer getEuiccControllerService() {
+ return new ServiceRegisterer("econtroller");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the eUICC card controller service.
+ */
+ @NonNull
+ public ServiceRegisterer getEuiccCardControllerServiceRegisterer() {
+ return new ServiceRegisterer("euicc_card_controller");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the ICC phone book service.
+ */
+ @NonNull
+ public ServiceRegisterer getIccPhoneBookServiceRegisterer() {
+ return new ServiceRegisterer("simphonebook");
+ }
+}
diff --git a/android-34/android/os/Temperature.java b/android-34/android/os/Temperature.java
new file mode 100644
index 0000000..a138431
--- /dev/null
+++ b/android-34/android/os/Temperature.java
@@ -0,0 +1,224 @@
+/*
+ * 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.hardware.thermal.TemperatureType;
+import android.hardware.thermal.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/aidl/android/hardware/thermal
+ * /ThrottlingSeverity.aidl */
+ 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,
+ TYPE_TPU,
+ TYPE_DISPLAY,
+ TYPE_MODEM,
+ TYPE_SOC
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /** Keep in sync with hardware/interfaces/thermal/aidl/android/hardware/thermal
+ * /TemperatureType.aidl */
+ 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;
+ public static final int TYPE_TPU = TemperatureType.TPU;
+ public static final int TYPE_DISPLAY = TemperatureType.DISPLAY;
+ public static final int TYPE_MODEM = TemperatureType.MODEM;
+ public static final int TYPE_SOC = TemperatureType.SOC;
+
+ /**
+ * 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_SOC;
+ }
+
+ /**
+ * 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(@Nullable 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-34/android/os/TestLooperManager.java b/android-34/android/os/TestLooperManager.java
new file mode 100644
index 0000000..5e7549f
--- /dev/null
+++ b/android-34/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-34/android/os/ThreadLocalWorkSource.java b/android-34/android/os/ThreadLocalWorkSource.java
new file mode 100644
index 0000000..e9adb20
--- /dev/null
+++ b/android-34/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<int []> sWorkSourceUid =
+ ThreadLocal.withInitial(() -> new int[] {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()[0];
+ }
+
+ /**
+ * 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.get()[0] = uid;
+ return token;
+ }
+
+ /**
+ * Restores the state using the provided token.
+ */
+ public static void restore(long token) {
+ sWorkSourceUid.get()[0] = 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()[0];
+ }
+
+ private ThreadLocalWorkSource() {
+ }
+}
diff --git a/android-34/android/os/TimestampedValue.java b/android-34/android/os/TimestampedValue.java
new file mode 100644
index 0000000..3d8a550
--- /dev/null
+++ b/android-34/android/os/TimestampedValue.java
@@ -0,0 +1,129 @@
+/*
+ * 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.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A value with an associated reference time. The reference time will typically be provided by the
+ * elapsed realtime clock. The elapsed realtime clock can be obtained using methods like
+ * {@link SystemClock#elapsedRealtime()} or {@link SystemClock#elapsedRealtimeClock()}.
+ * If a suitable clock is used the reference time can be used to identify the age of a value or
+ * ordering between values.
+ *
+ * <p>This class implements {@link Parcelable} for convenience but instances will only actually be
+ * parcelable if the value type held is {@code null}, {@link Parcelable}, or one of the other types
+ * supported by {@link Parcel#writeValue(Object)} / {@link Parcel#readValue(ClassLoader)}.
+ *
+ * @param <T> the type of the value with an associated timestamp
+ * @hide
+ */
+public final class TimestampedValue<T> implements Parcelable {
+ private final long mReferenceTimeMillis;
+ @Nullable
+ private final T mValue;
+
+ public TimestampedValue(long referenceTimeMillis, @Nullable T value) {
+ mReferenceTimeMillis = referenceTimeMillis;
+ mValue = value;
+ }
+
+ /** Returns the reference time value. See {@link TimestampedValue} for more information. */
+ public long getReferenceTimeMillis() {
+ return mReferenceTimeMillis;
+ }
+
+ /**
+ * Returns the value associated with the timestamp. See {@link TimestampedValue} for more
+ * information.
+ */
+ @Nullable
+ public T getValue() {
+ return mValue;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimestampedValue<?> that = (TimestampedValue<?>) o;
+ return mReferenceTimeMillis == that.mReferenceTimeMillis
+ && Objects.equals(mValue, that.mValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mReferenceTimeMillis, mValue);
+ }
+
+ @Override
+ public String toString() {
+ return "TimestampedValue{"
+ + "mReferenceTimeMillis=" + mReferenceTimeMillis
+ + ", mValue=" + mValue
+ + '}';
+ }
+
+ /**
+ * Returns the difference in milliseconds between two instance's reference times.
+ */
+ public static long referenceTimeDifference(
+ @NonNull TimestampedValue<?> one, @NonNull TimestampedValue<?> two) {
+ return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
+ }
+
+ /** @hide */
+ public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
+ new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
+
+ @Override
+ public TimestampedValue<?> createFromParcel(@NonNull Parcel source) {
+ return createFromParcel(source, null);
+ }
+
+ @Override
+ public TimestampedValue<?> createFromParcel(
+ @NonNull Parcel source, @Nullable ClassLoader classLoader) {
+ long referenceTimeMillis = source.readLong();
+ Object value = source.readValue(classLoader);
+ return new TimestampedValue<>(referenceTimeMillis, value);
+ }
+
+ @Override
+ public TimestampedValue[] newArray(int size) {
+ return new TimestampedValue[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mReferenceTimeMillis);
+ dest.writeValue(mValue);
+ }
+}
diff --git a/android-34/android/os/TokenWatcher.java b/android-34/android/os/TokenWatcher.java
new file mode 100644
index 0000000..00333da
--- /dev/null
+++ b/android-34/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-34/android/os/Trace.java b/android-34/android/os/Trace.java
new file mode 100644
index 0000000..0d0d1da
--- /dev/null
+++ b/android-34/android/os/Trace.java
@@ -0,0 +1,508 @@
+/*
+ * 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 static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import dalvik.annotation.optimization.CriticalNative;
+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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 */
+ @SystemApi(client = MODULE_LIBRARIES)
+ 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 */
+ @SystemApi
+ 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;
+ /** @hide */
+ public static final long TRACE_TAG_THERMAL = 1L << 27;
+
+ private static final long TRACE_TAG_NOT_READY = 1L << 63;
+ /** @hide **/
+ public static final int MAX_SECTION_NAME_LEN = 127;
+
+ // Must be volatile to avoid word tearing.
+ // This is only kept in case any apps get this by reflection but do not
+ // check the return value for null.
+ @UnsupportedAppUsage
+ private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
+
+ private static int sZygoteDebugFlags = 0;
+
+ @UnsupportedAppUsage
+ @CriticalNative
+ 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);
+ @FastNative
+ private static native void nativeAsyncTraceForTrackBegin(long tag,
+ String trackName, String name, int cookie);
+ @FastNative
+ private static native void nativeAsyncTraceForTrackEnd(long tag,
+ String trackName, int cookie);
+ @FastNative
+ private static native void nativeInstant(long tag, String name);
+ @FastNative
+ private static native void nativeInstantForTrack(long tag, String trackName, String name);
+
+ private Trace() {
+ }
+
+ /**
+ * 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
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static boolean isTagEnabled(long traceTag) {
+ long tags = nativeGetEnabledTags();
+ 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
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void traceCounter(long traceTag, @NonNull String counterName, int counterValue) {
+ if (isTagEnabled(traceTag)) {
+ nativeTraceCounter(traceTag, counterName, counterValue);
+ }
+ }
+
+ /**
+ * From Android S, this is no-op.
+ *
+ * Before, 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);
+ }
+
+ /**
+ * Set whether tracing is enabled in this process.
+ * @hide
+ */
+ public static void setTracingEnabled(boolean enabled, int debugFlags) {
+ nativeSetTracingEnabled(enabled);
+ sZygoteDebugFlags = debugFlags;
+ }
+
+ /**
+ * 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
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void traceBegin(long traceTag, @NonNull 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
+ @SystemApi(client = MODULE_LIBRARIES)
+ 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, name and cookie.
+ *
+ * If two events with the same methodName overlap in time then they *must* have
+ * different cookie values. If they do not, the trace can become corrupted
+ * in unpredictable ways.
+ *
+ * Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
+ * asynchronous events cannot be not nested. Consider using
+ * {@link #asyncTraceForTrackBegin(long, String, String, int)}
+ * if nested asynchronous events are needed.
+ *
+ * @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
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void asyncTraceBegin(long traceTag, @NonNull 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.
+ *
+ * See the documentation for {@link #asyncTraceBegin(long, String, int)}.
+ * for inteded usage of this method.
+ *
+ * @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
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void asyncTraceEnd(long traceTag, @NonNull String methodName, int cookie) {
+ if (isTagEnabled(traceTag)) {
+ nativeAsyncTraceEnd(traceTag, methodName, cookie);
+ }
+ }
+
+
+ /**
+ * Writes a trace message to indicate that a given section of code has
+ * begun. Must be followed by a call to {@link #asyncTraceForTrackEnd} using the same
+ * track name and cookie.
+ *
+ * Events with the same trackName and cookie nest inside each other in the
+ * same way as calls to {@link #traceBegin(long, String)} and
+ * {@link #traceEnd(long)}.
+ *
+ * If two events with the same trackName overlap in time but do not nest
+ * correctly, then they *must* have different cookie values. If they do not,
+ * the trace can become corrupted in unpredictable ways.
+ *
+ * Good Example:
+ *
+ * public void parent() {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "parent", mId);
+ * child()
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+ * }
+ *
+ * public void child() {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "child", mId);
+ * // Some code here.
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+ * }
+ *
+ * This would be visualized as so:
+ * [ Parent ]
+ * [ Child ]
+ *
+ * Bad Example:
+ *
+ * public static void processData(String dataToProcess) {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", 0);
+ * // Some code here.
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", 0);
+ * }
+ *
+ * public static void processDataInParallel({@code List<String>} data) {
+ * ExecutorService executor = Executors.newCachedThreadPool();
+ * for (String s : data) {
+ * pool.execute(() -> processData(s));
+ * }
+ * }
+ *
+ * This is invalid because it's possible for processData to be run many times
+ * in parallel (i.e. processData events overlap) but the same cookie is
+ * provided each time.
+ *
+ * To fix this, specify a different id in each invocation of processData:
+ *
+ * public static void processData(String dataToProcess, int id) {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", id);
+ * // Some code here.
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", id);
+ * }
+ *
+ * public static void processDataInParallel({@code List<String>} data) {
+ * ExecutorService executor = Executors.newCachedThreadPool();
+ * for (int i = 0; i < data.size(); ++i) {
+ * pool.execute(() -> processData(data.get(i), i));
+ * }
+ * }
+ *
+ * @param traceTag The trace tag.
+ * @param trackName The track where the event should appear in the trace.
+ * @param methodName The method name to appear in the trace.
+ * @param cookie Unique identifier used for nesting events on a single
+ * track. Events which overlap without nesting on the same
+ * track must have different values for cookie.
+ *
+ * @hide
+ */
+ public static void asyncTraceForTrackBegin(long traceTag,
+ @NonNull String trackName, @NonNull String methodName, int cookie) {
+ if (isTagEnabled(traceTag)) {
+ nativeAsyncTraceForTrackBegin(traceTag, trackName, methodName, cookie);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that the current method has ended.
+ * Must be called exactly once for each call to
+ * {@link #asyncTraceForTrackBegin(long, String, String, int)}
+ * using the same tag, track name, and cookie.
+ *
+ * See the documentation for {@link #asyncTraceForTrackBegin(long, String, String, int)}.
+ * for inteded usage of this method.
+ *
+ * @param traceTag The trace tag.
+ * @param trackName The track where the event should appear in the trace.
+ * @param cookie Unique identifier used for nesting events on a single
+ * track. Events which overlap without nesting on the same
+ * track must have different values for cookie.
+ *
+ * @hide
+ */
+ public static void asyncTraceForTrackEnd(long traceTag,
+ @NonNull String trackName, int cookie) {
+ if (isTagEnabled(traceTag)) {
+ nativeAsyncTraceForTrackEnd(traceTag, trackName, cookie);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code was invoked.
+ *
+ * @param traceTag The trace tag.
+ * @param methodName The method name to appear in the trace.
+ * @hide
+ */
+ public static void instant(long traceTag, String methodName) {
+ if (isTagEnabled(traceTag)) {
+ nativeInstant(traceTag, methodName);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code was invoked.
+ *
+ * @param traceTag The trace tag.
+ * @param trackName The track where the event should appear in the trace.
+ * @param methodName The method name to appear in the trace.
+ * @hide
+ */
+ public static void instantForTrack(long traceTag, String trackName, String methodName) {
+ if (isTagEnabled(traceTag)) {
+ nativeInstantForTrack(traceTag, trackName, methodName);
+ }
+ }
+
+ /**
+ * 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-34/android/os/TraceNameSupplier.java b/android-34/android/os/TraceNameSupplier.java
new file mode 100644
index 0000000..e4b3a4e
--- /dev/null
+++ b/android-34/android/os/TraceNameSupplier.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Supplier for custom trace messages.
+ *
+ * @hide
+ */
+public interface TraceNameSupplier {
+
+ /**
+ * Gets the name used for trace messages.
+ */
+ @NonNull String getTraceName();
+}
diff --git a/android-34/android/os/TracePerfTest.java b/android-34/android/os/TracePerfTest.java
new file mode 100644
index 0000000..0d64c39
--- /dev/null
+++ b/android-34/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-34/android/os/TransactionTooLargeException.java b/android-34/android/os/TransactionTooLargeException.java
new file mode 100644
index 0000000..4d5b2a1
--- /dev/null
+++ b/android-34/android/os/TransactionTooLargeException.java
@@ -0,0 +1,65 @@
+/*
+ * 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. {@link TransactionTooLargeException} is thrown as a
+ * heuristic when a transaction is large, and it fails, since these are the transactions
+ * which are most likely to overfill the transaction buffer.
+ * </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-34/android/os/TransactionTracker.java b/android-34/android/os/TransactionTracker.java
new file mode 100644
index 0000000..ebb4699
--- /dev/null
+++ b/android-34/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-34/android/os/UEventObserver.java b/android-34/android/os/UEventObserver.java
new file mode 100644
index 0000000..fa30e50
--- /dev/null
+++ b/android-34/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.compat.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-34/android/os/UidBatteryConsumer.java b/android-34/android/os/UidBatteryConsumer.java
new file mode 100644
index 0000000..103452d
--- /dev/null
+++ b/android-34/android/os/UidBatteryConsumer.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.text.TextUtils;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains power consumption data attributed to a specific UID.
+ *
+ * @hide
+ */
+public final class UidBatteryConsumer extends BatteryConsumer {
+
+ static final int CONSUMER_TYPE_UID = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ STATE_FOREGROUND,
+ STATE_BACKGROUND
+ })
+ public @interface State {
+ }
+
+ /**
+ * The state of an application when it is either running a foreground (top) activity.
+ */
+ public static final int STATE_FOREGROUND = 0;
+
+ /**
+ * The state of an application when it is running in the background, including the following
+ * states:
+ *
+ * {@link android.app.ActivityManager#PROCESS_STATE_IMPORTANT_BACKGROUND},
+ * {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND},
+ * {@link android.app.ActivityManager#PROCESS_STATE_BACKUP},
+ * {@link android.app.ActivityManager#PROCESS_STATE_SERVICE},
+ * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER},
+ * {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}.
+ */
+ public static final int STATE_BACKGROUND = 1;
+
+ static final int COLUMN_INDEX_UID = BatteryConsumer.COLUMN_COUNT;
+ static final int COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN = COLUMN_INDEX_UID + 1;
+ static final int COLUMN_INDEX_TIME_IN_FOREGROUND = COLUMN_INDEX_UID + 2;
+ static final int COLUMN_INDEX_TIME_IN_BACKGROUND = COLUMN_INDEX_UID + 3;
+ static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 4;
+
+ UidBatteryConsumer(BatteryConsumerData data) {
+ super(data);
+ }
+
+ private UidBatteryConsumer(@NonNull Builder builder) {
+ super(builder.mData, builder.mPowerComponentsBuilder.build());
+ }
+
+ public int getUid() {
+ return mData.getInt(COLUMN_INDEX_UID);
+ }
+
+ @Nullable
+ public String getPackageWithHighestDrain() {
+ return mData.getString(COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN);
+ }
+
+ /**
+ * Returns the amount of time in milliseconds this UID spent in the specified state.
+ */
+ public long getTimeInStateMs(@State int state) {
+ switch (state) {
+ case STATE_BACKGROUND:
+ return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND);
+ case STATE_FOREGROUND:
+ return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND);
+ }
+ return 0;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, boolean skipEmptyComponents) {
+ pw.print("UID ");
+ UserHandle.formatUid(pw, getUid());
+ pw.print(": ");
+ pw.print(BatteryStats.formatCharge(getConsumedPower()));
+
+ if (mData.layout.processStateDataIncluded) {
+ StringBuilder sb = new StringBuilder();
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ skipEmptyComponents);
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ skipEmptyComponents);
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ skipEmptyComponents);
+ appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_CACHED,
+ skipEmptyComponents);
+ pw.print(sb);
+ }
+
+ pw.print(" ( ");
+ mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */);
+ pw.print(" ) ");
+ }
+
+ private void appendProcessStateData(StringBuilder sb, @ProcessState int processState,
+ boolean skipEmptyComponents) {
+ Dimensions dimensions = new Dimensions(POWER_COMPONENT_ANY, processState);
+ final double power = mPowerComponents.getConsumedPower(dimensions);
+ if (power == 0 && skipEmptyComponents) {
+ return;
+ }
+
+ sb.append(" ").append(processStateToString(processState)).append(": ")
+ .append(BatteryStats.formatCharge(power));
+ }
+
+ static UidBatteryConsumer create(BatteryConsumerData data) {
+ return new UidBatteryConsumer(data);
+ }
+
+ /** Serializes this object to XML */
+ void writeToXml(TypedXmlSerializer serializer) throws IOException {
+ if (getConsumedPower() == 0) {
+ return;
+ }
+
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_UID);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_UID, getUid());
+ final String packageWithHighestDrain = getPackageWithHighestDrain();
+ if (!TextUtils.isEmpty(packageWithHighestDrain)) {
+ serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE,
+ packageWithHighestDrain);
+ }
+ serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND,
+ getTimeInStateMs(STATE_FOREGROUND));
+ serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND,
+ getTimeInStateMs(STATE_BACKGROUND));
+ mPowerComponents.writeToXml(serializer);
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_UID);
+ }
+
+ /** Parses an XML representation and populates the BatteryUsageStats builder */
+ static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder)
+ throws XmlPullParserException, IOException {
+ final int uid = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_UID);
+ final UidBatteryConsumer.Builder consumerBuilder =
+ builder.getOrCreateUidBatteryConsumerBuilder(uid);
+
+ int eventType = parser.getEventType();
+ if (eventType != XmlPullParser.START_TAG
+ || !parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) {
+ throw new XmlPullParserException("Invalid XML parser state");
+ }
+
+ consumerBuilder.setPackageWithHighestDrain(
+ parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE));
+ consumerBuilder.setTimeInStateMs(STATE_FOREGROUND,
+ parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND));
+ consumerBuilder.setTimeInStateMs(STATE_BACKGROUND,
+ parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND));
+ while (!(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(BatteryUsageStats.XML_TAG_UID))
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
+ PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder);
+ }
+ }
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * Builder for UidBatteryConsumer.
+ */
+ public static final class Builder extends BaseBuilder<Builder> {
+ private static final String PACKAGE_NAME_UNINITIALIZED = "";
+ private final BatteryStats.Uid mBatteryStatsUid;
+ private final int mUid;
+ private final boolean mIsVirtualUid;
+ private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED;
+ private boolean mExcludeFromBatteryUsageStats;
+
+ public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid) {
+ this(data, batteryStatsUid, batteryStatsUid.getUid());
+ }
+
+ public Builder(BatteryConsumerData data, int uid) {
+ this(data, null, uid);
+ }
+
+ private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid,
+ int uid) {
+ super(data, CONSUMER_TYPE_UID);
+ mBatteryStatsUid = batteryStatsUid;
+ mUid = uid;
+ mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID;
+ data.putLong(COLUMN_INDEX_UID, mUid);
+ }
+
+ @NonNull
+ public BatteryStats.Uid getBatteryStatsUid() {
+ if (mBatteryStatsUid == null) {
+ throw new IllegalStateException(
+ "UidBatteryConsumer.Builder was initialized without a BatteryStats.Uid");
+ }
+ return mBatteryStatsUid;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public boolean isVirtualUid() {
+ return mIsVirtualUid;
+ }
+
+ /**
+ * Sets the name of the package owned by this UID that consumed the highest amount
+ * of power since BatteryStats reset.
+ */
+ @NonNull
+ public Builder setPackageWithHighestDrain(@Nullable String packageName) {
+ mPackageWithHighestDrain = TextUtils.nullIfEmpty(packageName);
+ return this;
+ }
+
+ /**
+ * Sets the duration, in milliseconds, that this UID was active in a particular state,
+ * such as foreground or background.
+ */
+ @NonNull
+ public Builder setTimeInStateMs(@State int state, long timeInStateMs) {
+ switch (state) {
+ case STATE_FOREGROUND:
+ mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND, timeInStateMs);
+ break;
+ case STATE_BACKGROUND:
+ mData.putLong(COLUMN_INDEX_TIME_IN_BACKGROUND, timeInStateMs);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported state: " + state);
+ }
+ return this;
+ }
+
+ /**
+ * Marks the UidBatteryConsumer for exclusion from the result set.
+ */
+ public Builder excludeFromBatteryUsageStats() {
+ mExcludeFromBatteryUsageStats = true;
+ return this;
+ }
+
+ /**
+ * Adds power and usage duration from the supplied UidBatteryConsumer.
+ */
+ public Builder add(UidBatteryConsumer consumer) {
+ mPowerComponentsBuilder.addPowerAndDuration(consumer.mPowerComponents);
+
+ setTimeInStateMs(STATE_FOREGROUND,
+ mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND)
+ + consumer.getTimeInStateMs(STATE_FOREGROUND));
+ setTimeInStateMs(STATE_BACKGROUND,
+ mData.getLong(COLUMN_INDEX_TIME_IN_BACKGROUND)
+ + consumer.getTimeInStateMs(STATE_BACKGROUND));
+
+ if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) {
+ mPackageWithHighestDrain = consumer.getPackageWithHighestDrain();
+ } else if (!TextUtils.equals(mPackageWithHighestDrain,
+ consumer.getPackageWithHighestDrain())) {
+ // Consider combining two UidBatteryConsumers with this distribution
+ // of power drain between packages:
+ // (package1=100, package2=10) and (package1=100, package2=101).
+ // Since we don't know the actual power distribution between packages at this
+ // point, we have no way to correctly declare package1 as the winner.
+ // The naive logic of picking the consumer with the higher total consumed
+ // power would produce an incorrect result.
+ mPackageWithHighestDrain = null;
+ }
+ return this;
+ }
+
+ /**
+ * Returns true if this UidBatteryConsumer must be excluded from the
+ * BatteryUsageStats.
+ */
+ public boolean isExcludedFromBatteryUsageStats() {
+ return mExcludeFromBatteryUsageStats;
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public UidBatteryConsumer build() {
+ if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) {
+ mPackageWithHighestDrain = null;
+ }
+ if (mPackageWithHighestDrain != null) {
+ mData.putString(COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN, mPackageWithHighestDrain);
+ }
+ return new UidBatteryConsumer(this);
+ }
+ }
+}
diff --git a/android-34/android/os/UpdateEngine.java b/android-34/android/os/UpdateEngine.java
new file mode 100644
index 0000000..b7e3068
--- /dev/null
+++ b/android-34/android/os/UpdateEngine.java
@@ -0,0 +1,666 @@
+/*
+ * 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.SystemApi;
+import android.annotation.WorkerThread;
+import android.content.res.AssetFileDescriptor;
+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 validity 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;
+
+ /**
+ * Error code: there is not enough space on the device to apply the update. User should
+ * be prompted to free up space and re-try the update.
+ *
+ * <p>See {@link UpdateEngine#allocateSpace}.
+ */
+ public static final int NOT_ENOUGH_SPACE = 60;
+
+ /**
+ * Error code: the device is corrupted and no further updates may be applied.
+ *
+ * <p>See {@link UpdateEngine#cleanupAppliedPayload}.
+ */
+ public static final int DEVICE_CORRUPTED = 61;
+ }
+
+ /** @hide */
+ @IntDef(value = {
+ ErrorCodeConstants.SUCCESS,
+ ErrorCodeConstants.ERROR,
+ ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+ ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+ ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+ ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+ ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+ ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+ ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+ ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+ ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+ ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+ ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+ ErrorCodeConstants.NOT_ENOUGH_SPACE,
+ ErrorCodeConstants.DEVICE_CORRUPTED,
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * 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 final 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));
+ if (mUpdateEngine == null) {
+ throw new IllegalStateException("Failed to find update_engine");
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * Applies the payload passed as AssetFileDescriptor {@code assetFd}
+ * instead of using the {@code file://} scheme.
+ *
+ * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
+ * {@code headerKeyValuePairs} parameters.
+ */
+ public void applyPayload(@NonNull AssetFileDescriptor assetFd,
+ @NonNull String[] headerKeyValuePairs) {
+ try {
+ mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(),
+ assetFd.getStartOffset(), assetFd.getLength(), 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. Note this call will clear the entire update
+ * progress. So a subsequent {@link #applyPayload} will apply the update
+ * from scratch.
+ *
+ * <p>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();
+ }
+ }
+
+ /**
+ * Sets the A/B slot switch for the next boot after applying an ota update. If
+ * {@link #applyPayload} hasn't switched the slot, the updater APP can call
+ * this API to switch the slot and apply the update on next boot.
+ *
+ * @param payloadMetadataFilename the location of the metadata without the
+ * {@code file://} prefix.
+ */
+ public void setShouldSwitchSlotOnReboot(@NonNull String payloadMetadataFilename) {
+ try {
+ mUpdateEngine.setShouldSwitchSlotOnReboot(payloadMetadataFilename);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resets the boot slot to the source/current slot, without cancelling the
+ * update progress. This can be called after the update is installed, and to
+ * prevent the device from accidentally taking the update when it reboots.
+ *
+ * This is useful when users don't want to take the update immediately; or
+ * the updater determines some condition hasn't met, e.g. insufficient space
+ * for boot.
+ */
+ public void resetShouldSwitchSlotOnReboot() {
+ try {
+ mUpdateEngine.resetShouldSwitchSlotOnReboot();
+ } 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();
+ }
+ }
+
+ /**
+ * Return value of {@link #allocateSpace.}
+ */
+ public static final class AllocateSpaceResult {
+ private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS;
+ private long mFreeSpaceRequired = 0;
+ private AllocateSpaceResult() {}
+ /**
+ * Error code.
+ *
+ * @return The following error codes:
+ * <ul>
+ * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated
+ * successfully.</li>
+ * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient
+ * space.</li>
+ * <li>Other {@link ErrorCodeConstants} for other errors.</li>
+ * </ul>
+ */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Estimated total space that needs to be available on the userdata partition to apply the
+ * payload (in bytes).
+ *
+ * <p>
+ * Note that in practice, more space needs to be made available before applying the payload
+ * to keep the device working.
+ *
+ * @return The following values:
+ * <ul>
+ * <li>zero if {@link #getErrorCode} returns {@link ErrorCodeConstants#SUCCESS}</li>
+ * <li>non-zero if {@link #getErrorCode} returns
+ * {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}.
+ * Value is the estimated total space required on userdata partition.</li>
+ * </ul>
+ * @throws IllegalStateException if {@link #getErrorCode} is not one of the above.
+ *
+ */
+ public long getFreeSpaceRequired() {
+ if (mErrorCode == ErrorCodeConstants.SUCCESS) {
+ return 0;
+ }
+ if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) {
+ return mFreeSpaceRequired;
+ }
+ throw new IllegalStateException(String.format(
+ "getFreeSpaceRequired() is not available when error code is %d", mErrorCode));
+ }
+ }
+
+ /**
+ * Initialize partitions for a payload associated with the given payload
+ * metadata {@code payloadMetadataFilename} by preallocating required space.
+ *
+ * <p>This function should be called after payload has been verified after
+ * {@link #verifyPayloadMetadata}. This function does not verify whether
+ * the given payload is applicable or not.
+ *
+ * <p>Implementation of {@code allocateSpace} uses
+ * {@code headerKeyValuePairs} to determine whether space has been allocated
+ * for a different or same payload previously. If space has been allocated
+ * for a different payload before, space will be reallocated for the given
+ * payload. If space has been allocated for the same payload, no actions to
+ * storage devices are taken.
+ *
+ * <p>This function is synchronous and may take a non-trivial amount of
+ * time. Callers should call this function in a background thread.
+ *
+ * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}.
+ * @param headerKeyValuePairs See {@link #applyPayload}.
+ * @return See {@link AllocateSpaceResult#getErrorCode} and
+ * {@link AllocateSpaceResult#getFreeSpaceRequired}.
+ */
+ @WorkerThread
+ @NonNull
+ public AllocateSpaceResult allocateSpace(
+ @NonNull String payloadMetadataFilename,
+ @NonNull String[] headerKeyValuePairs) {
+ AllocateSpaceResult result = new AllocateSpaceResult();
+ try {
+ result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload(
+ payloadMetadataFilename,
+ headerKeyValuePairs);
+ result.mErrorCode = result.mFreeSpaceRequired == 0
+ ? ErrorCodeConstants.SUCCESS
+ : ErrorCodeConstants.NOT_ENOUGH_SPACE;
+ return result;
+ } catch (ServiceSpecificException e) {
+ result.mErrorCode = e.errorCode;
+ result.mFreeSpaceRequired = 0;
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class CleanupAppliedPayloadCallback extends IUpdateEngineCallback.Stub {
+ private int mErrorCode = ErrorCodeConstants.ERROR;
+ private boolean mCompleted = false;
+ private Object mLock = new Object();
+ private int getResult() {
+ synchronized (mLock) {
+ while (!mCompleted) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException ex) {
+ // do nothing, just wait again.
+ }
+ }
+ return mErrorCode;
+ }
+ }
+
+ @Override
+ public void onStatusUpdate(int status, float percent) {
+ }
+
+ @Override
+ public void onPayloadApplicationComplete(int errorCode) {
+ synchronized (mLock) {
+ mErrorCode = errorCode;
+ mCompleted = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Cleanup files used by the previous update and free up space after the
+ * device has been booted successfully into the new build.
+ *
+ * <p>In particular, this function waits until delta files for snapshots for
+ * Virtual A/B update are merged to OS partitions, then delete these delta
+ * files.
+ *
+ * <p>This function is synchronous and may take a non-trivial amount of
+ * time. Callers should call this function in a background thread.
+ *
+ * <p>This function does not delete payload binaries downloaded for a
+ * non-streaming OTA update.
+ *
+ * @return One of the following:
+ * <ul>
+ * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li>
+ * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred.
+ * The device should be able to recover after a reboot. The function should
+ * be retried after the reboot.</li>
+ * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is
+ * encountered. Device is corrupted, and future updates must not be applied.
+ * The device cannot recover without flashing and factory resets.
+ * </ul>
+ */
+ @WorkerThread
+ @ErrorCode
+ public int cleanupAppliedPayload() {
+ CleanupAppliedPayloadCallback callback = new CleanupAppliedPayloadCallback();
+ try {
+ mUpdateEngine.cleanupSuccessfulUpdate(callback);
+ return callback.getResult();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android-34/android/os/UpdateEngineCallback.java b/android-34/android/os/UpdateEngineCallback.java
new file mode 100644
index 0000000..7fe7024
--- /dev/null
+++ b/android-34/android/os/UpdateEngineCallback.java
@@ -0,0 +1,49 @@
+/*
+ * 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(
+ @UpdateEngine.ErrorCode int errorCode);
+}
diff --git a/android-34/android/os/UpdateLock.java b/android-34/android/os/UpdateLock.java
new file mode 100644
index 0000000..5aa9401
--- /dev/null
+++ b/android-34/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.compat.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean isHeld() {
+ synchronized (mToken) {
+ return mHeld;
+ }
+ }
+
+ /**
+ * Acquire an update lock.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/UserBatteryConsumer.java b/android-34/android/os/UserBatteryConsumer.java
new file mode 100644
index 0000000..6b4a5cf
--- /dev/null
+++ b/android-34/android/os/UserBatteryConsumer.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains power consumption data attributed to a {@link UserHandle}.
+ *
+ * {@hide}
+ */
+public class UserBatteryConsumer extends BatteryConsumer {
+ static final int CONSUMER_TYPE_USER = 2;
+
+ private static final int COLUMN_INDEX_USER_ID = BatteryConsumer.COLUMN_COUNT;
+
+ static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 1;
+
+ UserBatteryConsumer(BatteryConsumerData data) {
+ super(data);
+ }
+
+ private UserBatteryConsumer(@NonNull UserBatteryConsumer.Builder builder) {
+ super(builder.mData, builder.mPowerComponentsBuilder.build());
+ }
+
+ public int getUserId() {
+ return mData.getInt(COLUMN_INDEX_USER_ID);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, boolean skipEmptyComponents) {
+ final double consumedPower = getConsumedPower();
+ pw.print("User ");
+ pw.print(getUserId());
+ pw.print(": ");
+ pw.print(BatteryStats.formatCharge(consumedPower));
+ pw.print(" ( ");
+ mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */);
+ pw.print(" ) ");
+ }
+
+ /** Serializes this object to XML */
+ void writeToXml(TypedXmlSerializer serializer) throws IOException {
+ if (getConsumedPower() == 0) {
+ return;
+ }
+
+ serializer.startTag(null, BatteryUsageStats.XML_TAG_USER);
+ serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID, getUserId());
+ mPowerComponents.writeToXml(serializer);
+ serializer.endTag(null, BatteryUsageStats.XML_TAG_USER);
+ }
+
+ /** Parses an XML representation and populates the BatteryUsageStats builder */
+ static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder)
+ throws XmlPullParserException, IOException {
+ final int userId = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID);
+ final UserBatteryConsumer.Builder consumerBuilder =
+ builder.getOrCreateUserBatteryConsumerBuilder(userId);
+
+ int eventType = parser.getEventType();
+ if (eventType != XmlPullParser.START_TAG
+ || !parser.getName().equals(BatteryUsageStats.XML_TAG_USER)) {
+ throw new XmlPullParserException("Invalid XML parser state");
+ }
+ while (!(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(BatteryUsageStats.XML_TAG_USER))
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
+ PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder);
+ }
+ }
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * Builder for UserBatteryConsumer.
+ */
+ public static final class Builder extends BaseBuilder<Builder> {
+ private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
+
+ Builder(BatteryConsumerData data, int userId) {
+ super(data, CONSUMER_TYPE_USER);
+ data.putLong(COLUMN_INDEX_USER_ID, userId);
+ }
+
+ /**
+ * Add a UidBatteryConsumer to this UserBatteryConsumer.
+ * <p>
+ * Calculated power and duration components of the added UID battery consumers
+ * are aggregated at the time the UserBatteryConsumer is built by the {@link #build()}
+ * method.
+ * </p>
+ */
+ public void addUidBatteryConsumer(UidBatteryConsumer.Builder uidBatteryConsumerBuilder) {
+ if (mUidBatteryConsumers == null) {
+ mUidBatteryConsumers = new ArrayList<>();
+ }
+ mUidBatteryConsumers.add(uidBatteryConsumerBuilder);
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public UserBatteryConsumer build() {
+ if (mUidBatteryConsumers != null) {
+ for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
+ UidBatteryConsumer.Builder uidBatteryConsumer = mUidBatteryConsumers.get(i);
+ mPowerComponentsBuilder.addPowerAndDuration(
+ uidBatteryConsumer.mPowerComponentsBuilder);
+ }
+ }
+ return new UserBatteryConsumer(this);
+ }
+ }
+}
diff --git a/android-34/android/os/UserHandle.java b/android-34/android/os/UserHandle.java
new file mode 100644
index 0000000..ef39010
--- /dev/null
+++ b/android-34/android/os/UserHandle.java
@@ -0,0 +1,691 @@
+/*
+ * 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.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * 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
+ @TestApi
+ public static final @UserIdInt int USER_ALL = -1;
+
+ /** @hide A user handle to indicate all users on the device */
+ @SystemApi
+ public static final @NonNull UserHandle ALL = new UserHandle(USER_ALL);
+
+ /** @hide A user id to indicate the currently active user */
+ @UnsupportedAppUsage
+ @TestApi
+ public static final @UserIdInt int USER_CURRENT = -2;
+
+ /** @hide A user handle to indicate the current user of the device */
+ @SystemApi
+ 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
+ @TestApi
+ public static final @UserIdInt int USER_NULL = -10000;
+
+ private static final @NonNull UserHandle NULL = new UserHandle(USER_NULL);
+
+ /**
+ * @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
+ @TestApi
+ public static final @UserIdInt int USER_SYSTEM = 0;
+
+ /** @hide A user serial constant to indicate the "system" user of the device */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int USER_SERIAL_SYSTEM = 0;
+
+ /** @hide A user handle to indicate the "system" user of the device */
+ @SystemApi
+ 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 */
+ @TestApi
+ public static final int MIN_SECONDARY_USER_ID = 10;
+
+ /** @hide */
+ public static final int MAX_SECONDARY_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
+
+ /**
+ * (Arbitrary) user handle cache size.
+ * {@link #CACHED_USER_HANDLES} caches user handles in the range of
+ * [{@link #MIN_SECONDARY_USER_ID}, {@link #MIN_SECONDARY_USER_ID} + {@link #NUM_CACHED_USERS}).
+ *
+ * For other users, we cache UserHandles in {link #sExtraUserHandleCache}.
+ *
+ * Normally, {@link #CACHED_USER_HANDLES} should cover all existing users, but use
+ * {link #sExtraUserHandleCache} to ensure {@link UserHandle#of} will not cause too many
+ * object allocations even if the device happens to have a secondary user with a large number
+ * (e.g. the user kept creating and removing the guest user?).
+ */
+ private static final int NUM_CACHED_USERS = MU_ENABLED ? 8 : 0;
+
+ /** @see #NUM_CACHED_USERS} */
+ private static final UserHandle[] CACHED_USER_HANDLES = new UserHandle[NUM_CACHED_USERS];
+
+ /**
+ * Extra cache for users beyond CACHED_USER_HANDLES.
+ *
+ * @see #NUM_CACHED_USERS
+ * @hide
+ */
+ @GuardedBy("sExtraUserHandleCache")
+ @VisibleForTesting
+ public static final SparseArray<UserHandle> sExtraUserHandleCache = new SparseArray<>(0);
+
+ /**
+ * Max size of {@link #sExtraUserHandleCache}. Once it reaches this size, we select
+ * an element to remove at random.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static final int MAX_EXTRA_USER_HANDLE_CACHE_SIZE = 32;
+
+ static {
+ // Not lazily initializing the cache, so that we can share them across processes.
+ // (We'll create them in zygote.)
+ for (int i = 0; i < CACHED_USER_HANDLES.length; i++) {
+ CACHED_USER_HANDLES[i] = new UserHandle(MIN_SECONDARY_USER_ID + i);
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int ERR_GID = -1;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int AID_ROOT = android.os.Process.ROOT_UID;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int AID_APP_START = android.os.Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int AID_APP_END = android.os.Process.LAST_APPLICATION_UID;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int AID_SHARED_GID_START = android.os.Process.FIRST_SHARED_APPLICATION_GID;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
+
+ /** The userId represented by this UserHandle. */
+ @UnsupportedAppUsage
+ final @UserIdInt 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
+ */
+ @UnsupportedAppUsage
+ @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;
+ }
+ }
+
+ /**
+ * Whether a UID belongs to a shared app gid.
+ * @hide
+ */
+ public static boolean isSharedAppGid(int uid) {
+ return getAppIdFromSharedAppGid(uid) != -1;
+ }
+
+ /**
+ * 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
+ @TestApi
+ 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 */
+ @NonNull
+ public static int[] fromUserHandles(@NonNull List<UserHandle> users) {
+ int[] userIds = new int[users.size()];
+ for (int i = 0; i < userIds.length; ++i) {
+ userIds[i] = users.get(i).getIdentifier();
+ }
+ return userIds;
+ }
+
+ /** @hide */
+ @NonNull
+ public static List<UserHandle> toUserHandles(@NonNull int[] userIds) {
+ List<UserHandle> users = new ArrayList<>(userIds.length);
+ for (int i = 0; i < userIds.length; ++i) {
+ users.add(UserHandle.of(userIds[i]));
+ }
+ return users;
+ }
+
+ /** @hide */
+ @SystemApi
+ public static UserHandle of(@UserIdInt int userId) {
+ if (userId == USER_SYSTEM) {
+ return SYSTEM; // Most common.
+ }
+ // These are sequential; so use a switch. Maybe they'll be optimized to a table lookup.
+ switch (userId) {
+ case USER_ALL:
+ return ALL;
+
+ case USER_CURRENT:
+ return CURRENT;
+
+ case USER_CURRENT_OR_SELF:
+ return CURRENT_OR_SELF;
+ }
+ if (userId >= MIN_SECONDARY_USER_ID
+ && userId < (MIN_SECONDARY_USER_ID + CACHED_USER_HANDLES.length)) {
+ return CACHED_USER_HANDLES[userId - MIN_SECONDARY_USER_ID];
+ }
+ if (userId == USER_NULL) { // Not common.
+ return NULL;
+ }
+ return getUserHandleFromExtraCache(userId);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static UserHandle getUserHandleFromExtraCache(@UserIdInt int userId) {
+ synchronized (sExtraUserHandleCache) {
+ final UserHandle extraCached = sExtraUserHandleCache.get(userId);
+ if (extraCached != null) {
+ return extraCached;
+ }
+ if (sExtraUserHandleCache.size() >= MAX_EXTRA_USER_HANDLE_CACHE_SIZE) {
+ sExtraUserHandleCache.removeAt(
+ (new Random()).nextInt(MAX_EXTRA_USER_HANDLE_CACHE_SIZE));
+ }
+ final UserHandle newHandle = new UserHandle(userId);
+ sExtraUserHandleCache.put(userId, newHandle);
+ return newHandle;
+ }
+ }
+
+ /**
+ * Returns the uid that is composed from the userId and the appId.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public static int getUid(@UserIdInt int userId, @AppIdInt int appId) {
+ if (MU_ENABLED && appId >= 0) {
+ return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+ } else {
+ return appId;
+ }
+ }
+
+ /**
+ * Returns the uid representing the given appId for this UserHandle.
+ *
+ * @param appId the AppId to compose the uid
+ * @return the uid representing the given appId for this UserHandle
+ * @hide
+ */
+ @SystemApi
+ public int getUid(@AppIdInt int appId) {
+ return getUid(getIdentifier(), appId);
+ }
+
+ /**
+ * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+ * @hide
+ */
+ @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);
+ }
+
+ /**
+ * Returns the gid shared between all users with the app that this uid represents, or -1 if the
+ * uid is invalid.
+ * @hide
+ */
+ @SystemApi
+ public static int getSharedAppGid(int uid) {
+ return getSharedAppGid(getUserId(uid), getAppId(uid));
+ }
+
+ /** @hide */
+ public static int getSharedAppGid(@UserIdInt int userId, @AppIdInt 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(@UserIdInt int userId, @AppIdInt 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.
+ *
+ * @param uid The uid to format
+ * @return A string representing the UID with its individual components broken out
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ 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
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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(@UserIdInt int userId) {
+ mHandle = userId;
+ }
+
+ /**
+ * Returns the userId stored in this UserHandle.
+ * @hide
+ */
+ @SystemApi
+ public @UserIdInt int getIdentifier() {
+ return mHandle;
+ }
+
+ @Override
+ public String toString() {
+ return "UserHandle{" + mHandle + "}";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ try {
+ if (obj != null) {
+ UserHandle other = (UserHandle)obj;
+ return mHandle == other.mHandle;
+ }
+ } catch (ClassCastException ignore) {
+ }
+ 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) {
+ // Try to avoid allocation; use of() here. Keep this and the constructor below
+ // in sync.
+ return UserHandle.of(in.readInt());
+ }
+
+ 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(); // Keep this and createFromParcel() in sync.
+ }
+}
diff --git a/android-34/android/os/UserManager.java b/android-34/android/os/UserManager.java
new file mode 100644
index 0000000..7d68b44
--- /dev/null
+++ b/android-34/android/os/UserManager.java
@@ -0,0 +1,6136 @@
+/*
+ * 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 static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL;
+import static android.app.admin.DevicePolicyResources.UNDEFINED;
+
+import android.Manifest;
+import android.accounts.AccountManager;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
+import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.PropertyInvalidatedCache;
+import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.UnsupportedAppUsage;
+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.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.location.LocationManager;
+import android.provider.Settings;
+import android.util.AndroidException;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.internal.R;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 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 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;
+ /** Holding the Application context (not constructor param context). */
+ private final Context mContext;
+
+ /** The userId of the constructor param context. To be used instead of mContext.getUserId(). */
+ private final @UserIdInt int mUserId;
+
+ /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
+ private String mProfileTypeOfProcessUser = null;
+
+ /** Whether the device is in headless system user mode; null until cached. */
+ private static Boolean sIsHeadlessSystemUser = null;
+
+ /**
+ * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
+ * This type of user cannot be created; it can only pre-exist on first boot.
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+
+ /**
+ * User type representing a regular non-profile non-{@link UserHandle#USER_SYSTEM system} human
+ * user.
+ * This is sometimes called an ordinary 'secondary user'.
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
+
+ /**
+ * User type representing a guest user that may be transient.
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST";
+
+ /**
+ * User type representing a user for demo purposes only, which can be removed at any time.
+ * @hide
+ */
+ public static final String USER_TYPE_FULL_DEMO = "android.os.usertype.full.DEMO";
+
+ /**
+ * User type representing a "restricted profile" user, which is a full user that is subject to
+ * certain restrictions from a parent user. Note, however, that it is NOT technically a profile.
+ * @hide
+ */
+ public static final String USER_TYPE_FULL_RESTRICTED = "android.os.usertype.full.RESTRICTED";
+
+ /**
+ * User type representing a managed profile, which is a profile that is to be managed by a
+ * device policy controller (DPC).
+ * The intended purpose is for work profiles, which are managed by a corporate entity.
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
+
+ /**
+ * User type representing a clone profile. Clone profile is a user profile type used to run
+ * second instance of an otherwise single user App (eg, messengers). Only the primary user
+ * is allowed to have a clone profile.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
+
+ /**
+ * User type representing a generic profile for testing purposes. Only on debuggable builds.
+ * @hide
+ */
+ public static final String USER_TYPE_PROFILE_TEST = "android.os.usertype.profile.TEST";
+
+ /**
+ * User type representing a {@link UserHandle#USER_SYSTEM system} user that is <b>not</b> a
+ * human user.
+ * This type of user cannot be created; it can only pre-exist on first boot.
+ * @hide
+ */
+ @SystemApi
+ public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
+
+ /**
+ * Flag passed to {@link #requestQuietModeEnabled} to request disabling quiet mode only if
+ * there is no need to confirm the user credentials. If credentials are required to disable
+ * quiet mode, {@link #requestQuietModeEnabled} will do nothing and return {@code false}.
+ */
+ public static final int QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED = 0x1;
+
+ /**
+ * Flag passed to {@link #requestQuietModeEnabled} to request disabling quiet mode without
+ * asking for credentials. This is used when managed profile password is forgotten. It starts
+ * the user in locked state so that a direct boot aware DPC could reset the password.
+ * Should not be used together with
+ * {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED} or an exception will be thrown.
+ * @hide
+ */
+ public static final int QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL = 0x2;
+
+ /**
+ * List of flags available for the {@link #requestQuietModeEnabled} method.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "QUIET_MODE_" }, value = {
+ QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED,
+ QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL})
+ public @interface QuietModeFlag {}
+
+ /**
+ * @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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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 via Settings. This
+ * restriction does not affect Wi-Fi tethering settings.
+ *
+ * <p>A device owner and a profile owner can set this restriction, although the restriction has
+ * no effect in a managed profile. When it is set by a device owner, a profile owner on the
+ * primary user or by a profile owner of an organization-owned managed profile on the parent
+ * profile, it disallows the primary user from changing Wi-Fi access points.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_CONFIG_WIFI = "no_config_wifi";
+
+ /**
+ * Specifies if a user is disallowed from enabling/disabling Wi-Fi.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it applies globally - i.e., it disables airplane mode
+ * from changing Wi-Fi state.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_CHANGE_WIFI_STATE = "no_change_wifi_state";
+
+ /**
+ * Specifies if a user is disallowed from using Wi-Fi tethering.
+ *
+ * <p>This restriction does not limit the user's ability to modify or connect to regular
+ * Wi-Fi networks, which is separately controlled by {@link #DISALLOW_CONFIG_WIFI}.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from using
+ * Wi-Fi tethering. Other forms of tethering are not affected.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * This user restriction disables only Wi-Fi tethering.
+ * Use {@link #DISALLOW_CONFIG_TETHERING} to limit all forms of tethering.
+ * When {@link #DISALLOW_CONFIG_TETHERING} is set, this user restriction becomes obsolete.
+ *
+ * <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_WIFI_TETHERING = "no_wifi_tethering";
+
+ /**
+ * Specifies if a user is disallowed from being granted admin privileges.
+ *
+ * <p>This restriction limits ability of other admin users to grant admin
+ * privileges to selected user.
+ *
+ * <p>This restriction has no effect in a mode that does not allow multiple admins.
+ *
+ * <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_GRANT_ADMIN = "no_grant_admin";
+
+ /**
+ * Specifies if users are disallowed from sharing Wi-Fi for admin configured networks.
+ *
+ * <p>Device owner and profile owner can set this restriction.
+ * When it is set by any of these owners, it prevents all users from
+ * sharing Wi-Fi for networks configured by these owners.
+ * Other networks not configured by these owners are not affected.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_SHARING_ADMIN_CONFIGURED_WIFI =
+ "no_sharing_admin_configured_wifi";
+
+ /**
+ * Specifies if a user is disallowed from using Wi-Fi Direct.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from using
+ * Wi-Fi Direct.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_WIFI_DIRECT = "no_wifi_direct";
+
+ /**
+ * Specifies if a user is disallowed from adding a new Wi-Fi configuration.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from adding
+ * a new Wi-Fi configuration. This does not limit the owner and carrier's ability
+ * to add a new configuration.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_ADD_WIFI_CONFIG = "no_add_wifi_config";
+
+ /**
+ * Specifies if a user is disallowed from changing the device
+ * language. The default value is <code>false</code>.
+ *
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCALE}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>In a managed profile, location sharing by default reflects the primary user's setting, but
+ * can be overridden and forced off by setting this restriction to true in the managed profile.
+ *
+ * <p>A device owner and a profile owner can set this restriction. When it is set by a device
+ * owner, a profile owner on the primary user or by a profile owner of an organization-owned
+ * managed profile on the parent profile, it prevents the primary user from turning on
+ * location sharing.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_SHARE_LOCATION = "no_share_location";
+
+ /**
+ * Specifies if airplane mode is disallowed on the device.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it applies globally - i.e., it disables airplane mode
+ * on the entire device.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AIRPLANE_MODE}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DISPLAY}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DISPLAY}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DISPLAY}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * 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 via Settings. This does
+ * <em>not</em> restrict the user from turning bluetooth on or off.
+ *
+ * <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>A device owner and a profile owner can set this restriction, although the restriction has
+ * no effect in a managed profile. When it is set by a device owner, a profile owner on the
+ * primary user or by a profile owner of an organization-owned managed profile on the parent
+ * profile, it disallows the primary user from configuring bluetooth.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_CONFIG_BLUETOOTH = "no_config_bluetooth";
+
+ /**
+ * Specifies if bluetooth is disallowed on the device. If bluetooth is disallowed on the device,
+ * bluetooth cannot be turned on or configured via Settings.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally - i.e., it disables bluetooth on
+ * the entire device and all users will be affected. When it is set by a profile owner on the
+ * primary user or by a profile owner of an organization-owned managed profile on the parent
+ * profile, it disables the primary user from using bluetooth and configuring bluetooth
+ * in Settings.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>A device owner and a profile owner can set this restriction. When it is set by a device
+ * owner, it applies globally. When it is set by a profile owner on the primary user or by a
+ * profile owner of an organization-owned managed profile on the parent profile, it disables
+ * the primary user from any outgoing bluetooth sharing.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>Default is <code>true</code> for managed profiles and false otherwise.
+ *
+ * <p>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.
+ *
+ * <p>This restriction can only be set by a <a href="https://developers.google.com/android/work/terminology#device_owner_do">
+ * device owner</a> or a <a href="https://developers.google.com/android/work/terminology#profile_owner_po">
+ * profile owner</a> on the primary user's profile or a profile owner of an organization-owned
+ * <a href="https://developers.google.com/android/work/terminology#managed_profile">
+ * managed profile</a> on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from transferring files over USB. No other
+ * user on the device is able to use file transfer over USB because the UI for file transfer
+ * is always associated with the primary user.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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 admin user this specifies if the user can remove users.
+ * When set on a non-admin 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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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()
+ * @deprecated As the ability to have a managed profile on a fully-managed device has been
+ * removed from the platform, this restriction will be silently ignored when applied by the
+ * device owner.
+ * When the device is provisioned with a managed profile on an organization-owned device,
+ * the managed profile could not be removed anyway.
+ */
+ @Deprecated
+ public static final String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
+
+ /**
+ * Specifies if a user is disallowed from enabling or accessing debugging features.
+ *
+ * <p>A device owner and a profile owner can set this restriction. When it is set by a device
+ * owner, a profile owner on the primary user or by a profile owner of an organization-owned
+ * managed profile on the parent profile, it disables debugging features altogether, including
+ * USB debugging. When set on a managed profile or a secondary user, it blocks debugging for
+ * that user only, including starting activities, making service calls, accessing content
+ * providers, sending broadcasts, installing/uninstalling packages, clearing user data, etc.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_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>From Android 12 ({@linkplain android.os.Build.VERSION_CODES#S API level 31}) enforcing
+ * this restriction clears currently active VPN if it was configured by the user.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_VPN}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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 via Settings.
+ *
+ * <p>A device owner and a profile owner can set this restriction. When it is set by a device
+ * owner, a profile owner on the primary user or by a profile owner of an organization-owned
+ * managed profile on the parent profile, it disallows the primary user from turning location
+ * on or off.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+ * as a device owner or a profile owner can still enable or disable location mode via
+ * {@link DevicePolicyManager#setLocationEnabled} when this restriction is on.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see LocationManager#isLocationEnabled()
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_LOCATION = "no_config_location";
+
+ /**
+ * Specifies configuring date, time and timezone is disallowed via Settings.
+ *
+ * <p>A device owner and a profile owner can set this restriction, although the restriction has
+ * no effect in a managed profile. When it is set by a device owner or by a profile owner of an
+ * organization-owned managed profile on the parent profile, it applies globally - i.e.,
+ * it disables date, time and timezone setting on the entire device and all users are affected.
+ * When it is set by a profile owner on the primary user, it disables the primary user
+ * from configuring date, time and timezone and disables all configuring of date, time and
+ * timezone in Settings.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_TIME}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_CONFIG_DATE_TIME = "no_config_date_time";
+
+ /**
+ * Specifies if a user is disallowed from using and configuring Tethering and portable hotspots
+ * via Settings.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from using Tethering and hotspots and
+ * disables all configuring of Tethering and hotspots in Settings.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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 an admin user.
+ * The default value is <code>false</code>.
+ * <p>This restriction has no effect on non-admin users since they cannot factory reset the
+ * device.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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 or 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> When the device is an organization-owned device provisioned with a managed profile,
+ * this restriction will be set as a base restriction which cannot be removed by any admin.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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()
+ * @deprecated As the ability to have a managed profile on a fully-managed device has been
+ * removed from the platform, this restriction will be silently ignored when applied by the
+ * device owner.
+ */
+ @Deprecated
+ public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
+
+ /**
+ * Specifies if a user is disallowed from creating clone profile.
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ * @hide
+ */
+ public static final String DISALLOW_ADD_CLONE_PROFILE = "no_add_clone_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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from configuring cell broadcasts.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>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.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from configuring mobile networks.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from mounting physical external media.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
+
+ /**
+ * Specifies if a user is disallowed from adjusting microphone volume. If set, the microphone
+ * will be muted.
+ *
+ * <p>A device owner and a profile owner can set this restriction, although the restriction has
+ * no effect in a managed profile. When it is set by a device owner, it applies globally. When
+ * it is set by a profile owner on the primary user or by a profile owner of an
+ * organization-owned managed profile on the parent profile, it will disallow the primary user
+ * from adjusting the microphone volume.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MICROPHONE}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_UNMUTE_MICROPHONE = "no_unmute_microphone";
+
+ /**
+ * Specifies if a user is disallowed from adjusting the global volume. If set, the global 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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>A device owner and a profile owner can set this restriction, although the restriction has
+ * no effect in a managed profile. When it is set by a device owner, a profile owner on the
+ * primary user or by a profile owner of an organization-owned managed profile on the parent
+ * profile, it disallows the primary user from making outgoing phone calls.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CALLS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_OUTGOING_CALLS = "no_outgoing_calls";
+
+ /**
+ * Specifies that the user is not allowed to send or receive SMS messages.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from sending or receiving SMS messages.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SMS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FUN}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WINDOWS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WALLPAPER}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from rebooting the device into safe
+ * boot mode.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SAFE_BOOT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * @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.
+ *
+ * <p>A device owner and a profile owner can set this restriction. When it is set by a
+ * device owner, it applies globally - i.e., it disables the use of camera on the entire device
+ * and all users are affected. When it is set by a profile owner on the primary user or by a
+ * profile owner of an organization-owned managed profile on the parent profile, it disables
+ * the primary user from using camera.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CAMERA}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * @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 global volume.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * @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.
+ *
+ * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * user or a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by a device owner, it applies globally. When it is set by a profile owner
+ * on the primary user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the primary user from using cellular data when roaming.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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 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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AUTOFILL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>A device owner and a profile owner can set this restriction. When it is set by a device
+ * owner, a profile owner on the primary user or by a profile owner of an organization-owned
+ * managed profile on the parent profile, it disables the primary user's screen from being
+ * captured for artificial intelligence purposes.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SCREEN_CONTENT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>A device owner and a profile owner can set this restriction. When it is set by a device
+ * owner, a profile owner on the primary user or by a profile owner of an organization-owned
+ * managed profile on the parent profile, it disables the primary user from receiving content
+ * suggestions for selections based on the contents of their screen.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SCREEN_CONTENT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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>
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PRINTING}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * 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>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile. When it is set by either of these
+ * owners, it applies globally.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_CONFIG_PRIVATE_DNS =
+ "disallow_config_private_dns";
+
+ /**
+ * Specifies whether the microphone toggle is available to the user. If this restriction is set,
+ * the user will not be able to block microphone access via the system toggle. If microphone
+ * access is blocked when the restriction is added, it will be automatically re-enabled.
+ *
+ * This restriction can only be set by a device owner.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * @see android.hardware.SensorPrivacyManager
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_MICROPHONE_TOGGLE =
+ "disallow_microphone_toggle";
+
+ /**
+ * Specifies whether the camera toggle is available to the user. If this restriction is set,
+ * the user will not be able to block camera access via the system toggle. If camera
+ * access is blocked when the restriction is added, it will be automatically re-enabled.
+ *
+ * This restriction can only be set by a device owner.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * @see android.hardware.SensorPrivacyManager
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CAMERA_TOGGLE =
+ "disallow_camera_toggle";
+
+ /**
+ * This is really not a user restriction in the normal sense. This can't be set to a user,
+ * via UserManager nor via DevicePolicyManager. This is not even set in UserSettingsUtils.
+ * This is defined here purely for convenience within the settings app.
+ *
+ * TODO(b/191306258): Refactor the Settings app to remove the need for this field, and delete it
+ *
+ * Specifies whether biometrics are available to the user. This is used internally only,
+ * as a means of communications between biometric settings and
+ * {@link com.android.settingslib.enterprise.ActionDisabledByAdminControllerFactory}.
+ *
+ * @see {@link android.hardware.biometrics.ParentalControlsUtilsInternal}
+ * @see {@link com.android.settings.biometrics.ParentalControlsUtils}
+ *
+ * @hide
+ */
+ public static final String DISALLOW_BIOMETRIC = "disallow_biometric";
+
+ /**
+ * Specifies whether the user is allowed to modify default apps in settings.
+ *
+ * <p>This restriction can be set by device or profile owner.
+ *
+ * <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_CONFIG_DEFAULT_APPS = "disallow_config_default_apps";
+
+ /**
+ * 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";
+
+ /**
+ * Specifies if a user is not allowed to use 2g networks.
+ *
+ * <p>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile.
+ * In all cases, the setting applies globally on the device and will prevent the device from
+ * scanning for or connecting to 2g networks, except in the case of an emergency.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <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_CELLULAR_2G = "no_cellular_2g";
+
+ /**
+ * This user restriction specifies if Ultra-wideband is disallowed on the device. If
+ * Ultra-wideband is disallowed it cannot be turned on via Settings.
+ *
+ * <p>
+ * Ultra-wideband (UWB) is a radio technology that can use a very low energy level
+ * for short-range, high-bandwidth communications over a large portion of the radio spectrum.
+ *
+ * <p>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile.
+ * In both cases, the restriction applies globally on the device and will turn off the
+ * ultra-wideband radio if it's currently on and prevent the radio from being turned on in
+ * the future.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>Default 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_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
+
+ /**
+ * List of key values that can be passed into the various user restriction related methods
+ * in {@link UserManager} & {@link DevicePolicyManager}.
+ * Note: This is slightly different from the real set of user restrictions listed in {@link
+ * com.android.server.pm.UserRestrictionsUtils#USER_RESTRICTIONS}. For example
+ * {@link #KEY_RESTRICTIONS_PENDING} is not a real user restriction, but is a legitimate
+ * value that can be passed into {@link #hasUserRestriction(String)}.
+ * @hide
+ */
+ @StringDef(value = {
+ DISALLOW_MODIFY_ACCOUNTS,
+ DISALLOW_CONFIG_WIFI,
+ DISALLOW_CONFIG_LOCALE,
+ DISALLOW_INSTALL_APPS,
+ DISALLOW_UNINSTALL_APPS,
+ DISALLOW_SHARE_LOCATION,
+ DISALLOW_AIRPLANE_MODE,
+ DISALLOW_CONFIG_BRIGHTNESS,
+ DISALLOW_AMBIENT_DISPLAY,
+ DISALLOW_CONFIG_SCREEN_TIMEOUT,
+ DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
+ DISALLOW_CONFIG_BLUETOOTH,
+ DISALLOW_BLUETOOTH,
+ DISALLOW_BLUETOOTH_SHARING,
+ DISALLOW_USB_FILE_TRANSFER,
+ DISALLOW_CONFIG_CREDENTIALS,
+ DISALLOW_REMOVE_USER,
+ DISALLOW_REMOVE_MANAGED_PROFILE,
+ DISALLOW_DEBUGGING_FEATURES,
+ DISALLOW_CONFIG_VPN,
+ DISALLOW_CONFIG_LOCATION,
+ DISALLOW_CONFIG_DATE_TIME,
+ DISALLOW_CONFIG_TETHERING,
+ DISALLOW_NETWORK_RESET,
+ DISALLOW_FACTORY_RESET,
+ DISALLOW_ADD_USER,
+ DISALLOW_ADD_MANAGED_PROFILE,
+ DISALLOW_ADD_CLONE_PROFILE,
+ ENSURE_VERIFY_APPS,
+ DISALLOW_CONFIG_CELL_BROADCASTS,
+ DISALLOW_CONFIG_MOBILE_NETWORKS,
+ DISALLOW_APPS_CONTROL,
+ DISALLOW_MOUNT_PHYSICAL_MEDIA,
+ DISALLOW_UNMUTE_MICROPHONE,
+ DISALLOW_ADJUST_VOLUME,
+ DISALLOW_OUTGOING_CALLS,
+ DISALLOW_SMS,
+ DISALLOW_FUN,
+ DISALLOW_CREATE_WINDOWS,
+ DISALLOW_SYSTEM_ERROR_DIALOGS,
+ DISALLOW_CROSS_PROFILE_COPY_PASTE,
+ DISALLOW_OUTGOING_BEAM,
+ DISALLOW_WALLPAPER,
+ DISALLOW_SET_WALLPAPER,
+ DISALLOW_SAFE_BOOT,
+ DISALLOW_RECORD_AUDIO,
+ DISALLOW_RUN_IN_BACKGROUND,
+ DISALLOW_CAMERA,
+ DISALLOW_UNMUTE_DEVICE,
+ DISALLOW_DATA_ROAMING,
+ DISALLOW_SET_USER_ICON,
+ DISALLOW_OEM_UNLOCK,
+ DISALLOW_UNIFIED_PASSWORD,
+ ALLOW_PARENT_PROFILE_APP_LINKING,
+ DISALLOW_AUTOFILL,
+ DISALLOW_CONTENT_CAPTURE,
+ DISALLOW_CONTENT_SUGGESTIONS,
+ DISALLOW_USER_SWITCH,
+ DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+ DISALLOW_PRINTING,
+ DISALLOW_CONFIG_PRIVATE_DNS,
+ DISALLOW_MICROPHONE_TOGGLE,
+ DISALLOW_CAMERA_TOGGLE,
+ KEY_RESTRICTIONS_PENDING,
+ DISALLOW_BIOMETRIC,
+ DISALLOW_CHANGE_WIFI_STATE,
+ DISALLOW_WIFI_TETHERING,
+ DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ DISALLOW_WIFI_DIRECT,
+ DISALLOW_ADD_WIFI_CONFIG,
+ DISALLOW_CELLULAR_2G,
+ DISALLOW_ULTRA_WIDEBAND_RADIO,
+ DISALLOW_GRANT_ADMIN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserRestrictionKey {}
+
+ /**
+ * Property used to override whether the device uses headless system user mode.
+ *
+ * <p>Only used on non-user builds.
+ *
+ * <p><b>NOTE: </b>setting this variable directly won't properly change the headless system user
+ * mode behavior and might put the device in a bad state; the system user mode should be changed
+ * using {@code cmd user set-system-user-mode-emulation} instead.
+ *
+ * @hide
+ */
+ public static final String SYSTEM_USER_MODE_EMULATION_PROPERTY =
+ "persist.debug.user_mode_emulation";
+
+ /** @hide */
+ public static final String SYSTEM_USER_MODE_EMULATION_DEFAULT = "default";
+ /** @hide */
+ public static final String SYSTEM_USER_MODE_EMULATION_FULL = "full";
+ /** @hide */
+ public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
+
+ /**
+ * System Property used to override whether users can be created even if their type is disabled
+ * or their limit is reached. Set value to 1 to enable.
+ *
+ * <p>Only used on non-user builds.
+ *
+ * @hide
+ */
+ public static final String DEV_CREATE_OVERRIDE_PROPERTY = "debug.user.creation_override";
+
+ private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
+
+ /**
+ * Action to start an activity to create a supervised user.
+ * Only devices with non-empty config_supervisedUserCreationPackage support this.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ public static final String ACTION_CREATE_SUPERVISED_USER =
+ "android.os.action.CREATE_SUPERVISED_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 // 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 switchability.
+ * @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 {}
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * the specified user has been successfully removed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_REMOVED = 0;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * the specified user is marked so that it will be removed when the user is stopped or on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_DEFERRED = 1;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * the specified user is already in the process of being removed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_ALREADY_BEING_REMOVED = 2;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * an unknown error occurred that prevented the user from being removed or set as ephemeral.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_ERROR_UNKNOWN = -1;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * the user could not be removed due to a {@link #DISALLOW_REMOVE_MANAGED_PROFILE} or
+ * {@link #DISALLOW_REMOVE_USER} user restriction.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_ERROR_USER_RESTRICTION = -2;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * user being removed does not exist.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_ERROR_USER_NOT_FOUND = -3;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * user being removed is a {@link UserHandle#SYSTEM} user which can't be removed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_ERROR_SYSTEM_USER = -4;
+
+ /**
+ * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
+ * user being removed is a {@link UserInfo#FLAG_MAIN} user and can't be removed because
+ * system property {@link com.android.internal.R.bool.isMainUserPermanentAdmin} is true.
+ * @hide
+ */
+ @SystemApi
+ public static final int REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN = -5;
+
+ /**
+ * Possible response codes from {@link #removeUserWhenPossible(UserHandle, boolean)}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "REMOVE_RESULT_" }, value = {
+ REMOVE_RESULT_REMOVED,
+ REMOVE_RESULT_DEFERRED,
+ REMOVE_RESULT_ALREADY_BEING_REMOVED,
+ REMOVE_RESULT_ERROR_USER_RESTRICTION,
+ REMOVE_RESULT_ERROR_USER_NOT_FOUND,
+ REMOVE_RESULT_ERROR_SYSTEM_USER,
+ REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN,
+ REMOVE_RESULT_ERROR_UNKNOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RemoveResult {}
+
+ /**
+ * 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;
+
+ /**
+ * Indicates user operation failed because a user with that account already exists.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
+
+ /**
+ * 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,
+ USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
+ })
+ 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;
+ }
+
+ /**
+ * Returns a UserOperationException containing the same message and error code.
+ * @hide
+ */
+ public static UserOperationException from(ServiceSpecificException exception) {
+ return new UserOperationException(exception.getMessage(), exception.errorCode);
+ }
+ }
+
+ /**
+ * Converts the ServiceSpecificException into a UserOperationException or throws null;
+ *
+ * @param exception exception to convert.
+ * @param throwInsteadOfNull if an exception should be thrown or null returned.
+ * @return null if chosen not to throw exception.
+ * @throws UserOperationException
+ */
+ private <T> T returnNullOrThrowUserOperationException(ServiceSpecificException exception,
+ boolean throwInsteadOfNull) throws UserOperationException {
+ if (throwInsteadOfNull) {
+ throw UserOperationException.from(exception);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Thrown to indicate user operation failed. (Checked exception)
+ * @hide
+ */
+ public static class CheckedUserOperationException extends AndroidException {
+ private final @UserOperationResult int mUserOperationResult;
+
+ /**
+ * Constructs a CheckedUserOperationException with specific result code.
+ *
+ * @param message the detail message
+ * @param userOperationResult the result code
+ * @hide
+ */
+ public CheckedUserOperationException(String message,
+ @UserOperationResult int userOperationResult) {
+ super(message);
+ mUserOperationResult = userOperationResult;
+ }
+
+ /** Returns the operation result code. */
+ public @UserOperationResult int getUserOperationResult() {
+ return mUserOperationResult;
+ }
+
+ /** Return a ServiceSpecificException containing the same message and error code. */
+ public ServiceSpecificException toServiceSpecificException() {
+ return new ServiceSpecificException(mUserOperationResult, getMessage());
+ }
+ }
+
+ /**
+ * For apps targeting {@link Build.VERSION_CODES#TIRAMISU} and above, any UserManager API marked
+ * as {@link android.annotation.UserHandleAware @UserHandleAware} will use the context user
+ * (rather than the calling user).
+ * For apps targeting an SDK version <em>below</em> this, the behaviour
+ * depends on the particular method and when it was first introduced:
+ * <ul>
+ * <li>
+ * if the {@literal @}UserHandleAware specifies a
+ * {@link android.annotation.UserHandleAware#enabledSinceTargetSdkVersion} of
+ * {@link Build.VERSION_CODES#TIRAMISU} the <em>calling</em> user is used.
+ * </li>
+ * <li>
+ * if the {@literal @}UserHandleAware doesn't specify a
+ * {@link android.annotation.UserHandleAware#enabledSinceTargetSdkVersion}, the
+ * <em>context</em> user is used.
+ * </li>
+ * <li>there should currently be no other values used by UserManager for
+ * {@link android.annotation.UserHandleAware#enabledSinceTargetSdkVersion}, since all
+ * old implicitly user-dependant APIs were updated in that version and anything
+ * introduced more recently should already be {@literal @}UserHandleAware.
+ * </li>
+ * </ul>
+ *
+ * Note that when an API marked with
+ * {@link android.annotation.UserHandleAware#enabledSinceTargetSdkVersion} is run
+ * on a device whose OS predates that version, the calling user will be used, since on such a
+ * device, the API is not {@literal @}UserHandleAware yet.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long ALWAYS_USE_CONTEXT_USER = 183155436L;
+
+ /**
+ * Returns the context user or the calling user, depending on the target SDK.
+ * New APIs do not require such gating and therefore should always use mUserId instead.
+ * @see #ALWAYS_USE_CONTEXT_USER
+ */
+ private @UserIdInt int getContextUserIfAppropriate() {
+ if (CompatChanges.isChangeEnabled(ALWAYS_USE_CONTEXT_USER)) {
+ return mUserId;
+ } else {
+ final int callingUser = UserHandle.myUserId();
+ if (callingUser != mUserId) {
+ Log.w(TAG, "Using the calling user " + callingUser
+ + ", rather than the specified context user " + mUserId
+ + ", because API is only UserHandleAware on higher targetSdkVersions.",
+ new Throwable());
+ }
+ return callingUser;
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static UserManager get(Context context) {
+ return (UserManager) context.getSystemService(Context.USER_SERVICE);
+ }
+
+ /** @hide */
+ public UserManager(Context context, IUserManager service) {
+ mService = service;
+ Context appContext = context.getApplicationContext();
+ mContext = (appContext == null ? context : appContext);
+ mUserId = context.getUserId();
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * @return Whether guest user is always ephemeral
+ * @hide
+ */
+ public static boolean isGuestUserAlwaysEphemeral() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
+ }
+
+ /**
+ * @return true, when we want to enable user manager API and UX to allow
+ * guest user ephemeral state change based on user input
+ * @hide
+ */
+ public static boolean isGuestUserAllowEphemeralStateChange() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_guestUserAllowEphemeralStateChange);
+ }
+
+ /**
+ * Returns whether multiple admins are enabled on the device
+ * @hide
+ */
+ public static boolean isMultipleAdminEnabled() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableMultipleAdmins);
+ }
+
+ /**
+ * Checks whether the device is running in a headless system user mode.
+ *
+ * <p>Headless system user mode means the {@link #isSystemUser() system user} runs system
+ * services and some system UI, but it is not associated with any real person and additional
+ * users must be created to be associated with real persons.
+ *
+ * @return whether the device is running in a headless system user mode.
+ */
+ public static boolean isHeadlessSystemUserMode() {
+ // No need for synchronization. Once it becomes non-null, it'll be non-null forever.
+ // (Its value is determined when UMS is constructed and cannot change.)
+ // Worst case we might end up calling the AIDL method multiple times but that's fine.
+ if (sIsHeadlessSystemUser == null) {
+ // Unfortunately this API is static, but the property no longer is. So go fetch the UMS.
+ try {
+ final IUserManager service = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ sIsHeadlessSystemUser = service.isHeadlessSystemUserMode();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return sIsHeadlessSystemUser;
+ }
+
+ /**
+ * @deprecated use {@link #getUserSwitchability()} instead.
+ *
+ * @removed
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @UserHandleAware
+ public boolean canSwitchUsers() {
+ try {
+ return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether switching users is currently allowed for the context user.
+ * <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(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public @UserSwitchabilityResult int getUserSwitchability() {
+ return getUserSwitchability(UserHandle.of(getContextUserIfAppropriate()));
+ }
+
+ /**
+ * Returns whether switching users is currently allowed for the provided user.
+ * <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
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
+ try {
+ return mService.getUserSwitchability(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the userId for the context user.
+ *
+ * @return the userId of the context user.
+ *
+ * @deprecated To get the <em>calling</em> user, use {@link UserHandle#myUserId()}.
+ * To get the <em>context</em> user, get it directly from the context.
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ // *** Do NOT use this in UserManager. Instead always use mUserId. ***
+ public @UserIdInt int getUserHandle() {
+ return getContextUserIfAppropriate();
+ }
+
+ /**
+ * Returns the userId for the user that this process is running under
+ * (<em>not</em> the context user).
+ *
+ * @return the userId of <em>this process</em>.
+ *
+ * @deprecated Use {@link UserHandle#myUserId()}
+ * @hide
+ */
+ @Deprecated
+ // NOT @UserHandleAware
+ public @UserIdInt int getProcessUserId() {
+ return UserHandle.myUserId();
+ }
+
+ /**
+ * @return the user type of the context user.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public @NonNull String getUserType() {
+ UserInfo userInfo = getUserInfo(mUserId);
+ return userInfo == null ? "" : userInfo.userType;
+ }
+
+ /**
+ * Returns the user name of the context user. This call is only available to applications on
+ * the system image.
+ *
+ * @return the user name
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ public @NonNull String getUserName() {
+ if (UserHandle.myUserId() == mUserId) {
+ try {
+ return mService.getUserName();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ } else {
+ UserInfo userInfo = getUserInfo(mUserId);
+ if (userInfo != null && userInfo.name != null) {
+ return userInfo.name;
+ }
+ return "";
+ }
+ }
+
+ /**
+ * 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
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ public boolean isUserNameSet() {
+ try {
+ return mService.isUserNameSet(getContextUserIfAppropriate());
+ } 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>
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#R}, this method always returns
+ * {@code false} in order to protect goat privacy.</p>
+ *
+ * @return Returns whether the user making this call is a goat.
+ */
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public boolean isUserAGoat() {
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) {
+ return false;
+ }
+ // Caution: This is NOT @UserHandleAware (because mContext is getApplicationContext and
+ // can hold a different userId), but for R+ it returns false, so it doesn't matter anyway.
+ return mContext.getPackageManager()
+ .isPackageAvailable("com.coffeestainstudios.goatsimulator");
+ }
+
+ /**
+ * Used to check if the context user is the primary user. The primary user is the first human
+ * user on a device. This is not supported in headless system user mode.
+ *
+ * @return whether the context user is the primary user.
+ *
+ * @deprecated This method always returns true for the system user, who may not be a full user
+ * if {@link #isHeadlessSystemUserMode} is true. Use {@link #isSystemUser}, {@link #isAdminUser}
+ * or {@link #isMainUser} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public boolean isPrimaryUser() {
+ final UserInfo user = getUserInfo(getContextUserIfAppropriate());
+ return user != null && user.isPrimary();
+ }
+
+ /**
+ * Used to check if the context user is 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 the context user is the system user.
+ */
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public boolean isSystemUser() {
+ return getContextUserIfAppropriate() == UserHandle.USER_SYSTEM;
+ }
+
+ /**
+ * Returns {@code true} if the context user is the designated "main user" of the device. This
+ * user may have access to certain features which are limited to at most one user. There will
+ * never be more than one main user on a device.
+ *
+ * <p>Currently, on most form factors the first human user on the device will be the main user;
+ * in the future, the concept may be transferable, so a different user (or even no user at all)
+ * may be designated the main user instead. On other form factors there might not be a main
+ * user.
+ *
+ * <p>Note that this will not be the system user on devices for which
+ * {@link #isHeadlessSystemUserMode()} returns true.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public boolean isMainUser() {
+ final UserInfo user = getUserInfo(mUserId);
+ return user != null && user.isMain();
+ }
+
+ /**
+ * Returns the designated "main user" of the device, or {@code null} if there is no main user.
+ *
+ * @see #isMainUser()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public @Nullable UserHandle getMainUser() {
+ try {
+ final int mainUserId = mService.getMainUserId();
+ if (mainUserId == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(mainUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Used to check if the context user is an admin user. An admin user may be 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 the context user is an admin user.
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public boolean isAdminUser() {
+ try {
+ return mService.isAdminUser(getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Returns whether the provided user is an admin user. There can be more than one admin
+ * user.
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public boolean isUserAdmin(@UserIdInt int userId) {
+ UserInfo user = getUserInfo(userId);
+ return user != null && user.isAdmin();
+ }
+
+ /**
+ * Returns whether the context user is of the given user type.
+ *
+ * @param userType the name of the user's user type, e.g.
+ * {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return true if the user is of the given user type.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public boolean isUserOfType(@NonNull String userType) {
+ try {
+ return mService.isUserOfType(mUserId, userType);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the user type is a
+ * {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}.
+ * @hide
+ */
+ public static boolean isUserTypeManagedProfile(@Nullable String userType) {
+ return USER_TYPE_PROFILE_MANAGED.equals(userType);
+ }
+
+ /**
+ * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}.
+ * @hide
+ */
+ public static boolean isUserTypeGuest(@Nullable String userType) {
+ return USER_TYPE_FULL_GUEST.equals(userType);
+ }
+
+ /**
+ * Returns whether the user type is a
+ * {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}.
+ * @hide
+ */
+ public static boolean isUserTypeRestricted(@Nullable String userType) {
+ return USER_TYPE_FULL_RESTRICTED.equals(userType);
+ }
+
+ /**
+ * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}.
+ * @hide
+ */
+ public static boolean isUserTypeDemo(@Nullable String userType) {
+ return USER_TYPE_FULL_DEMO.equals(userType);
+ }
+
+ /**
+ * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
+ * @hide
+ */
+ public static boolean isUserTypeCloneProfile(@Nullable String userType) {
+ return USER_TYPE_PROFILE_CLONE.equals(userType);
+ }
+
+ /**
+ * @hide
+ * @deprecated Use {@link #isRestrictedProfile()}
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS}
+ )
+ public boolean isLinkedUser() {
+ return isRestrictedProfile();
+ }
+
+ /**
+ * Used to check if the context user is a restricted profile. Restricted profiles
+ * may have a reduced number of available apps, app restrictions, and account restrictions.
+ *
+ * @return whether the context user is a restricted profile.
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS}
+ )
+ public boolean isRestrictedProfile() {
+ try {
+ return mService.isRestricted(getContextUserIfAppropriate());
+ } 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(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS},
+ conditional = true)
+ public boolean isRestrictedProfile(@NonNull UserHandle user) {
+ try {
+ return mService.isRestricted(user.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the calling context user can have a restricted profile.
+ * @return whether the context user can have a restricted profile.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware
+ public boolean canHaveRestrictedProfile() {
+ try {
+ return mService.canHaveRestrictedProfile(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the context user has at least one restricted profile associated with it.
+ * @return whether the user has a restricted profile associated with it
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public boolean hasRestrictedProfiles() {
+ try {
+ return mService.hasRestrictedProfiles(getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the parent of a restricted profile.
+ *
+ * @return the parent of the user or {@code null} if the user is not restricted profile
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public @Nullable UserHandle getRestrictedProfileParent() {
+ final UserInfo info = getUserInfo(mUserId);
+ if (info == null) return null;
+ if (!info.isRestricted()) return null;
+ final int parent = info.restrictedProfileParentId;
+ if (parent == UserHandle.USER_NULL) return null;
+ return UserHandle.of(parent);
+ }
+
+ /**
+ * Checks if a user is a guest user.
+ * @return whether user is a guest user.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public boolean isGuestUser(@UserIdInt int userId) {
+ UserInfo user = getUserInfo(userId);
+ return user != null && user.isGuest();
+ }
+
+ /**
+ * Used to check if the context user is a guest user. A guest user may be transient.
+ *
+ * @return whether the context user is a guest user.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public boolean isGuestUser() {
+ UserInfo user = getUserInfo(getContextUserIfAppropriate());
+ return user != null && user.isGuest();
+ }
+
+
+ /**
+ * Checks if the context user is 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 context user is a demo user.
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresPermissionIfNotCaller = android.Manifest.permission.MANAGE_USERS
+ )
+ public boolean isDemoUser() {
+ try {
+ return mService.isDemoUser(getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the calling context user is running in a profile. A profile is a user that
+ * typically has its own separate data but shares its UI with some parent user. For example, a
+ * {@link #isManagedProfile() managed profile} is a type of profile.
+ *
+ * @return whether the caller is in a profile.
+ */
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public boolean isProfile() {
+ return isProfile(mUserId);
+ }
+
+ private boolean isProfile(@UserIdInt int userId) {
+ final String profileType = getProfileType(userId);
+ return profileType != null && !profileType.equals("");
+ }
+
+ /**
+ * Returns the user type of the context user if it is a profile.
+ *
+ * This is a more specific form of {@link #getUserType()} with relaxed permission requirements.
+ *
+ * @return the user type of the context user if it is a {@link #isProfile() profile},
+ * an empty string if it is not a profile,
+ * or null if the user doesn't exist.
+ */
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ private @Nullable String getProfileType() {
+ return getProfileType(mUserId);
+ }
+
+ /** @see #getProfileType() */
+ private @Nullable String getProfileType(@UserIdInt int userId) {
+ // First, the typical case (i.e. the *process* user, not necessarily the context user).
+ // This cache cannot be become invalidated since it's about the calling process itself.
+ if (userId == UserHandle.myUserId()) {
+ // 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 (mProfileTypeOfProcessUser != null) {
+ return mProfileTypeOfProcessUser;
+ }
+ try {
+ final String profileType = mService.getProfileType(userId);
+ if (profileType != null) {
+ return mProfileTypeOfProcessUser = profileType.intern();
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ // The userId is not for the process's user. Use a slower cache that handles invalidation.
+ return mProfileTypeCache.query(userId);
+ }
+
+ /**
+ * Checks if the context user is a managed profile.
+ *
+ * Note that this applies specifically to <em>managed</em> profiles. For profiles in general,
+ * use {@link #isProfile()} instead.
+ *
+ * @return whether the context user is a managed profile.
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public boolean isManagedProfile() {
+ return isManagedProfile(getContextUserIfAppropriate());
+ }
+
+ /**
+ * Checks if the specified user is a managed profile.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
+ * {@link android.Manifest.permission#QUERY_USERS} permission, otherwise the caller
+ * must be in the same profile group of specified user.
+ *
+ * Note that this applies specifically to <em>managed</em> profiles. For profiles in general,
+ * use {@link #isProfile()} instead.
+ *
+ * @return whether the specified user is a managed profile.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isManagedProfile(@UserIdInt int userId) {
+ return isUserTypeManagedProfile(getProfileType(userId));
+ }
+
+ /**
+ * Checks if the context user is a clone profile.
+ *
+ * @return whether the context user is a clone profile.
+ *
+ * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ @SuppressAutoDoc
+ public boolean isCloneProfile() {
+ return isUserTypeCloneProfile(getProfileType());
+ }
+
+ /**
+ * Checks if the context user is an ephemeral user.
+ *
+ * @return whether the context user is an ephemeral user.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public boolean isEphemeralUser() {
+ return isUserEphemeral(mUserId);
+ }
+
+ /**
+ * Returns whether the specified user is ephemeral.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ 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.
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isUserRunning(UserHandle user) {
+ return isUserRunning(user.getIdentifier());
+ }
+
+ /** @hide */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ 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.
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ 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();
+ }
+ }
+
+ /**
+ * Checks if the context user is running in the foreground.
+ *
+ * @return whether the context user is running in the foreground.
+ */
+ @UserHandleAware
+ public boolean isUserForeground() {
+ try {
+ return mService.isUserForeground(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isVisibleBackgroundUsersEnabled() {
+ return SystemProperties.getBoolean("fw.visible_bg_users",
+ Resources.getSystem()
+ .getBoolean(R.bool.config_multiuserVisibleBackgroundUsers));
+ }
+
+ /**
+ * Returns whether the device allows (full) users to be started in background visible in a given
+ * display (which would allow them to launch activities in that display).
+ *
+ * @return {@code false} for most devices, except on automotive builds for vehiches with
+ * passenger displays.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleBackgroundUsersSupported() {
+ return isVisibleBackgroundUsersEnabled();
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isVisibleBackgroundUsersOnDefaultDisplayEnabled() {
+ return SystemProperties.getBoolean("fw.visible_bg_users_on_default_display",
+ Resources.getSystem()
+ .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+ }
+
+ /**
+ * Returns whether the device allows (full) users to be started in background visible in the
+ * {@link android.view.Display#DEFAULT_DISPLAY default display}.
+ *
+ * @return {@code false} for most devices, except passenger-only automotive build (i.e., when
+ * Android runs in a separate system in the back seat to manage the passenger displays).
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported() {
+ return isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ }
+
+ /**
+ * Checks if the user is visible at the moment.
+ *
+ * <p>Roughly speaking, a "visible user" is a user that can present UI on at least one display.
+ * It includes:
+ *
+ * <ol>
+ * <li>The current foreground user.
+ * <li>(Running) profiles of the current foreground user.
+ * <li>Background users assigned to secondary displays (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
+ * </ol>
+ *
+ * @return whether the user is visible at the moment, as defined above.
+ *
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.MANAGE_USERS"
+ })
+ public boolean isUserVisible() {
+ try {
+ return mService.isUserVisible(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the visible users (as defined by {@link #isUserVisible()}.
+ *
+ * @return visible users at the moment.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.MANAGE_USERS"
+ })
+ public @NonNull Set<UserHandle> getVisibleUsers() {
+ ArraySet<UserHandle> result = new ArraySet<>();
+ try {
+ int[] visibleUserIds = mService.getVisibleUsers();
+ if (visibleUserIds != null) {
+ for (int userId : visibleUserIds) {
+ result.add(UserHandle.of(userId));
+ }
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * See {@link com.android.server.pm.UserManagerInternal#getMainDisplayAssignedToUser(int)}.
+ *
+ * @hide
+ */
+ @TestApi
+ public int getMainDisplayIdAssignedToUser() {
+ try {
+ return mService.getMainDisplayIdAssignedToUser();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether the context 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()
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}
+ )
+ public boolean isUserUnlocked() {
+ return isUserUnlocked(getContextUserIfAppropriate());
+ }
+
+ /**
+ * 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 profile associated with it.
+ *
+ * @param user to retrieve the unlocked state for.
+ * @see Intent#ACTION_USER_UNLOCKED
+ * @see Context#createDeviceProtectedStorageContext()
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isUserUnlocked(UserHandle user) {
+ return isUserUnlocked(user.getIdentifier());
+ }
+
+ private static final String CACHE_KEY_IS_USER_UNLOCKED_PROPERTY =
+ "cache_key.is_user_unlocked";
+
+ private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockedCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(
+ 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
+ @Override
+ public Boolean recompute(Integer query) {
+ try {
+ return mService.isUserUnlocked(query);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ // Uses IS_USER_UNLOCKED_PROPERTY for invalidation as the APIs have the same dependencies.
+ private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockingOrUnlockedCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(
+ 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
+ @Override
+ public Boolean recompute(Integer query) {
+ try {
+ return mService.isUserUnlockingOrUnlocked(query);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ /** @hide */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isUserUnlocked(@UserIdInt int userId) {
+ return mIsUserUnlockedCache.query(userId);
+ }
+
+ /** @hide */
+ public void disableIsUserUnlockedCache() {
+ mIsUserUnlockedCache.disableLocal();
+ mIsUserUnlockingOrUnlockedCache.disableLocal();
+ }
+
+ /** @hide */
+ public static final void invalidateIsUserUnlockedCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
+ }
+
+ /**
+ * Return whether the provided user is already running in an
+ * "unlocked" state or in the process of unlocking.
+ * <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 profile associated with it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isUserUnlockingOrUnlocked(@NonNull UserHandle user) {
+ return isUserUnlockingOrUnlocked(user.getIdentifier());
+ }
+
+ /** @hide */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
+ return mIsUserUnlockingOrUnlockedCache.query(userId);
+ }
+
+ /**
+ * Return the time when the calling user started in elapsed milliseconds since boot,
+ * or 0 if not started.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ // NOT @UserHandleAware
+ public long getUserStartRealtime() {
+ if (getContextUserIfAppropriate() != UserHandle.myUserId()) {
+ // Note: If we want to support this in the future, also annotate with
+ // @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ throw new IllegalArgumentException("Calling from a context differing from the calling "
+ + "user is not currently supported.");
+ }
+ try {
+ return mService.getUserStartRealtime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the time when the context user was unlocked elapsed milliseconds since boot,
+ * or 0 if not unlocked.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ // NOT @UserHandleAware
+ public long getUserUnlockRealtime() {
+ if (getContextUserIfAppropriate() != UserHandle.myUserId()) {
+ // Note: If we want to support this in the future, also annotate with
+ // @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ throw new IllegalArgumentException("Calling from a context differing from the calling "
+ + "user is not currently supported.");
+ }
+ try {
+ return mService.getUserUnlockRealtime();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the UserInfo object describing a specific user.
+ * @param userId the user handle of the user whose information is being requested.
+ * @return the UserInfo object for a specific user.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public UserInfo getUserInfo(@UserIdInt int userId) {
+ try {
+ return mService.getUserInfo(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a {@link UserProperties} object describing the properties of the given user.
+ *
+ * Note that the caller may not have permission to access all items; requesting any item for
+ * which permission is lacking will throw a {@link SecurityException}.
+ *
+ * <p> Requires
+ * {@code android.Manifest.permission#MANAGE_USERS},
+ * {@code android.Manifest.permission#QUERY_USERS}, or
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * permission, or else the caller must be in the same profile group as the caller.
+ *
+ * @param userHandle the user handle of the user whose information is being requested.
+ * @return a UserProperties object for a specific user.
+ * @throws IllegalArgumentException if {@code userHandle} doesn't correspond to an existing user
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
+ return mUserPropertiesCache.query(userHandle.getIdentifier());
+ }
+
+ /**
+ * @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(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public int getUserRestrictionSource(@UserRestrictionKey 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(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ public List<EnforcingUser> getUserRestrictionSources(
+ @UserRestrictionKey String restrictionKey, UserHandle userHandle) {
+ try {
+ return mService.getUserRestrictionSources(restrictionKey, userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user-wide restrictions imposed on the context user.
+ * @return a Bundle containing all the restrictions.
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}
+ )
+ public Bundle getUserRestrictions() {
+ try {
+ return mService.getUserRestrictions(getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * <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 profile associated with it.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ 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.
+ */
+ @TestApi
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public boolean hasBaseUserRestriction(@UserRestrictionKey @NonNull String restrictionKey,
+ @NonNull 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 on the context user.
+ * 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
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public void setUserRestriction(String key, boolean value) {
+ try {
+ mService.setUserRestriction(key, value, getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the value of a specific restriction on a specific user.
+ * @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
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ 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 context 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 context user has the given restriction, {@code false} otherwise.
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}
+ )
+ public boolean hasUserRestriction(@UserRestrictionKey String restrictionKey) {
+ return hasUserRestrictionForUser(restrictionKey, getContextUserIfAppropriate());
+ }
+
+ /**
+ * @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.
+ * @deprecated Use {@link #hasUserRestrictionForUser(String, UserHandle)} instead.
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ @Deprecated
+ public boolean hasUserRestriction(@UserRestrictionKey String restrictionKey,
+ UserHandle userHandle) {
+ return hasUserRestrictionForUser(restrictionKey, userHandle);
+ }
+
+ /**
+ * 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.
+ *
+ * <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 profile associated with it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public boolean hasUserRestrictionForUser(@NonNull @UserRestrictionKey String restrictionKey,
+ @NonNull UserHandle userHandle) {
+ return hasUserRestrictionForUser(restrictionKey, userHandle.getIdentifier());
+ }
+
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ private boolean hasUserRestrictionForUser(@NonNull @UserRestrictionKey String restrictionKey,
+ @UserIdInt int userId) {
+ try {
+ return mService.hasUserRestriction(restrictionKey, userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Returns whether any user on the device has the given user restriction set.
+ */
+ public boolean hasUserRestrictionOnAnyUser(@UserRestrictionKey String restrictionKey) {
+ try {
+ return mService.hasUserRestrictionOnAnyUser(restrictionKey);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ *
+ * Checks whether changing the given setting to the given value is prohibited
+ * by the corresponding user restriction in the given user.
+ *
+ * May only be called by the OS itself.
+ *
+ * @return {@code true} if the change is prohibited, {@code false} if the change is allowed.
+ */
+ public boolean isSettingRestrictedForUser(String setting, @UserIdInt int userId,
+ String value, int callingUid) {
+ try {
+ return mService.isSettingRestrictedForUser(setting, userId, value, callingUid);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Register a binder callback for user restrictions changes.
+ * May only be called by the OS itself.
+ */
+ public void addUserRestrictionsListener(final IUserRestrictionsListener listener) {
+ try {
+ mService.addUserRestrictionsListener(listener);
+ } 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.
+ * Default user restrictions will be applied.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param name the user's name
+ * @param flags UserInfo 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.
+ * @throws IllegalArgumentException if flags do not correspond to a valid user type.
+ * @deprecated Use {@link #createUser(String, String, int)} instead.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ public @Nullable UserInfo createUser(@Nullable String name, @UserInfoFlag int flags) {
+ return createUser(name, UserInfo.getDefaultUserType(flags), flags);
+ }
+
+ /**
+ * Creates a user with the specified name and options.
+ * Default user restrictions will be applied.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
+ * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
+ * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION.
+ *
+ * @param name the user's name
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
+ * @param flags UserInfo flags that specify user properties.
+ * @return the {@link UserInfo} object for the created user, or {@code null} if the user
+ * could not be created.
+ *
+ * @see UserInfo
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @TestApi
+ public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType,
+ @UserInfoFlag int flags) {
+ try {
+ return mService.createUserWithThrow(name, userType, flags);
+ } catch (ServiceSpecificException e) {
+ return null;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a user with the specified {@link NewUserRequest}.
+ *
+ * @param newUserRequest specify the user information
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @NonNull NewUserResponse createUser(@NonNull NewUserRequest newUserRequest) {
+ try {
+ final UserHandle userHandle = mService.createUserWithAttributes(
+ newUserRequest.getName(),
+ newUserRequest.getUserType(),
+ newUserRequest.getFlags(),
+ newUserRequest.getUserIcon(),
+ newUserRequest.getAccountName(),
+ newUserRequest.getAccountType(),
+ newUserRequest.getAccountOptions());
+
+ return new NewUserResponse(userHandle, USER_OPERATION_SUCCESS);
+
+ } catch (ServiceSpecificException e) {
+ Log.w(TAG, "Exception while creating user " + newUserRequest, e);
+ return new NewUserResponse(null, e.errorCode);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Pre-creates a user of the specified type. Default user restrictions will be applied.
+ *
+ * <p>This method can be used by OEMs to "warm" up the user creation by pre-creating some users
+ * at the first boot, so they when the "real" user is created (for example,
+ * by {@link #createUser(String, String, int)} or {@link #createGuest(Context)}), it
+ * takes less time.
+ *
+ * <p>This method completes the majority of work necessary for user creation: it
+ * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+ * grants default permissions. When pre-created users become "real" users, only then are
+ * components notified of new user creation by firing user creation broadcasts.
+ *
+ * <p>All pre-created users are removed during system upgrade.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
+ * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
+ * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION.
+ *
+ *
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
+ * @return the {@link UserInfo} object for the created user.
+ *
+ * @throws UserOperationException if the user could not be created.
+ *
+ * @deprecated Pre-created users are deprecated. This method should no longer be used, and will
+ * be removed once all the callers are removed.
+ *
+ * @hide
+ */
+ @Deprecated
+ @TestApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @NonNull UserInfo preCreateUser(@NonNull String userType)
+ throws UserOperationException {
+ Log.w(TAG, "preCreateUser(): Pre-created user is deprecated.");
+ try {
+ return mService.preCreateUserWithThrow(userType);
+ } catch (ServiceSpecificException e) {
+ throw UserOperationException.from(e);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a guest user and configures it.
+ * @param context an application context
+ * @return the {@link UserInfo} object for the created user, or {@code null} if the user
+ * could not be created.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @Nullable UserInfo createGuest(Context context) {
+ try {
+ final UserInfo guest = mService.createUserWithThrow(null, USER_TYPE_FULL_GUEST, 0);
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+
+ if (UserManager.isGuestUserAllowEphemeralStateChange()) {
+ // Mark guest as (changeably) ephemeral if REMOVE_GUEST_ON_EXIT is 1
+ // This is done so that a user via a UI controller can choose to
+ // make a guest as ephemeral or not.
+ // Settings.Global.REMOVE_GUEST_ON_EXIT holds the choice on what the guest state
+ // should be, with default being ephemeral.
+ boolean resetGuestOnExit = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT, 1) == 1;
+
+ if (resetGuestOnExit && !guest.isEphemeral()) {
+ setUserEphemeral(guest.id, true);
+ }
+ }
+ return guest;
+ } catch (ServiceSpecificException e) {
+ return null;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ // TODO(b/256690588): Remove this after removing its callsites.
+ /**
+ * Gets the existing guest user if it exists. This does not include guest users that are dying.
+ * @return The existing guest user if it exists. Null otherwise.
+ * @hide
+ *
+ * @deprecated Use {@link #getGuestUsers()}
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public UserInfo findCurrentGuestUser() {
+ try {
+ final List<UserInfo> guestUsers = mService.getGuestUsers();
+ if (guestUsers.size() == 0) {
+ return null;
+ }
+ return guestUsers.get(0);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the existing guest users. This does not include guest users that are dying.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public @NonNull List<UserInfo> getGuestUsers() {
+ try {
+ return mService.getGuestUsers();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a user with the specified name and options as a profile of the context's user.
+ *
+ * @param name the user's name.
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @param disallowedPackages packages to not install for this profile.
+ *
+ * @return the {@link android.os.UserHandle} object for the created user,
+ * or throws {@link UserOperationException} if the user could not be created
+ * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above
+ * (otherwise returns {@code null}).
+ *
+ * @throws UserOperationException if the user could not be created and the calling app is
+ * targeting {@link android.os.Build.VERSION_CODES#R} or above.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @UserHandleAware
+ public @Nullable UserHandle createProfile(@NonNull String name, @NonNull String userType,
+ @NonNull Set<String> disallowedPackages) throws UserOperationException {
+ try {
+ return mService.createProfileForUserWithThrow(name, userType, 0,
+ mUserId, disallowedPackages.toArray(
+ new String[disallowedPackages.size()])).getUserHandle();
+ } catch (ServiceSpecificException e) {
+ return returnNullOrThrowUserOperationException(e,
+ mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a user with the specified name and options as a profile of another user.
+ * <p>Requires MANAGE_USERS. CREATE_USERS suffices for ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @param userId 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.
+ * @throws IllegalArgumentException if flags do not correspond to a valid user type.
+ * @deprecated Use {@link #createProfileForUser(String, String, int, int)} instead.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @Deprecated
+ public UserInfo createProfileForUser(String name, @UserInfoFlag int flags,
+ @UserIdInt int userId) {
+ return createProfileForUser(name, UserInfo.getDefaultUserType(flags), flags,
+ userId, null);
+ }
+
+ /**
+ * Creates a user with the specified name and options as a profile of another user.
+ *
+ * @param name the user's name
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @param flags UserInfo flags that specify user properties.
+ * @param userId 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
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @Nullable UserInfo createProfileForUser(String name, @NonNull String userType,
+ @UserInfoFlag int flags, @UserIdInt int userId) {
+ return createProfileForUser(name, userType, flags, userId, null);
+ }
+
+ /**
+ * Version of {@link #createProfileForUser(String, 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 userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @param flags UserInfo flags that specify user properties.
+ * @param userId 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 {@code null} if the user could
+ * not be created.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @Nullable UserInfo createProfileForUser(@Nullable String name, @NonNull String userType,
+ @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) {
+ try {
+ return mService.createProfileForUserWithThrow(name, userType, flags, userId,
+ disallowedPackages);
+ } catch (ServiceSpecificException e) {
+ return null;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Similar to {@link #createProfileForUser(String, String, int, int, String[])}
+ * except bypassing the checking of {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
+ *
+ * @see #createProfileForUser(String, String, int, int, String[])
+ * @hide
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @Nullable UserInfo createProfileForUserEvenWhenDisallowed(String name,
+ @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId,
+ String[] disallowedPackages) {
+ try {
+ return mService.createProfileForUserEvenWhenDisallowedWithThrow(name, userType, flags,
+ userId, disallowedPackages);
+ } catch (ServiceSpecificException e) {
+ return null;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a restricted profile with the specified name. This method also sets necessary
+ * restrictions and adds shared accounts (with the context user).
+ *
+ * @param name profile's name
+ * @return the {@link UserInfo} object for the created user, or {@code null} if the user
+ * could not be created.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @UserHandleAware
+ public @Nullable UserInfo createRestrictedProfile(@Nullable String name) {
+ try {
+ final int parentUserId = mUserId;
+ final UserInfo profile = mService.createRestrictedProfileWithThrow(name, parentUserId);
+ final UserHandle parentUserHandle = UserHandle.of(parentUserId);
+ AccountManager.get(mContext).addSharedAccountsFromParentUser(parentUserHandle,
+ UserHandle.of(profile.id));
+ return profile;
+ } catch (ServiceSpecificException e) {
+ return null;
+ } 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.
+ *
+ * This API should only be called if the current user is an {@link #isAdminUser() admin} user,
+ * as otherwise the returned intent will not be able to create a 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;
+ }
+
+ /**
+ * Returns the list of the system packages that would be installed on this type of user upon
+ * its creation.
+ *
+ * Returns {@code null} if all system packages would be installed.
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("NullableCollection")
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public @Nullable Set<String> getPreInstallableSystemPackages(@NonNull String userType) {
+ try {
+ final String[] installableSystemPackages
+ = mService.getPreInstallableSystemPackages(userType);
+ if (installableSystemPackages == null) {
+ return null;
+ }
+ return new ArraySet<>(installableSystemPackages);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ *
+ * Returns the preferred account name for the context user's creation.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public String getSeedAccountName() {
+ try {
+ return mService.getSeedAccountName(getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ *
+ * Returns the preferred account type for the context user's creation.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public String getSeedAccountType() {
+ try {
+ return mService.getSeedAccountType(getContextUserIfAppropriate());
+ } 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 context user.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public PersistableBundle getSeedAccountOptions() {
+ try {
+ return mService.getSeedAccountOptions(getContextUserIfAppropriate());
+ } 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)
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ 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 the context user.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public void clearSeedAccountData() {
+ try {
+ mService.clearSeedAccountData(getContextUserIfAppropriate());
+ } 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 userId
+ * @return
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean markGuestForDeletion(@UserIdInt int userId) {
+ try {
+ return mService.markGuestForDeletion(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the user as enabled, if such an user exists.
+ *
+ * <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
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ 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>Note that this does not alter the user's pre-existing user restrictions.
+ *
+ * @param userId 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 userId) {
+ try {
+ mService.setUserAdmin(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Revokes admin privileges from the user, if such a user exists.
+ *
+ * <p>Note that this does not alter the user's pre-existing user restrictions.
+ *
+ * @param userId the id of the user to revoke admin rights from
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.MANAGE_USERS
+ })
+ public void revokeUserAdmin(@UserIdInt int userId) {
+ try {
+ mService.revokeUserAdmin(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Evicts the user's credential encryption key from memory by stopping and restarting the user.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public void evictCredentialEncryptionKey(@UserIdInt int userId) {
+ try {
+ mService.evictCredentialEncryptionKey(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the number of users currently created on the device.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public int getUserCount() {
+ List<UserInfo> users = getUsers();
+ return users != null ? users.size() : 1;
+ }
+
+ /**
+ * Returns information for all fully-created users on this device, including ones marked for
+ * deletion.
+ *
+ * <p>To retrieve only users that are not marked for deletion, use {@link #getAliveUsers()}.
+ *
+ * <p>To retrieve *all* users (including partial and pre-created users), use
+ * {@link #getUsers(boolean, boolean, boolean)) getUsers(false, false, false)}.
+ *
+ * <p>To retrieve a more specific list of users, use
+ * {@link #getUsers(boolean, boolean, boolean)}.
+ *
+ * @return the list of users that were created.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ @TestApi
+ public @NonNull List<UserInfo> getUsers() {
+ return getUsers(/*excludePartial= */ true, /* excludeDying= */ false,
+ /* excludePreCreated= */ true);
+ }
+
+ /**
+ * Returns information for all "usable" users on this device (i.e, it excludes users that are
+ * marked for deletion, pre-created users, etc...).
+ *
+ * <p>To retrieve all fully-created users, use {@link #getUsers()}.
+ *
+ * <p>To retrieve a more specific list of users, use
+ * {@link #getUsers(boolean, boolean, boolean)}.
+ *
+ * @return the list of users that were created.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ @TestApi
+ public @NonNull List<UserInfo> getAliveUsers() {
+ return getUsers(/*excludePartial= */ true, /* excludeDying= */ true,
+ /* excludePreCreated= */ true);
+ }
+
+ /**
+ * @deprecated use {@link #getAliveUsers()} for {@code getUsers(true)}, or
+ * {@link #getUsers()} for @code getUsers(false)}.
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
+ return getUsers(/*excludePartial= */ true, excludeDying,
+ /* excludePreCreated= */ true);
+ }
+
+ /**
+ * Returns information for all users on this device, based on the filtering parameters.
+ *
+ * @deprecated Pre-created users are deprecated and no longer supported.
+ * Use {@link #getUsers()}, or {@link #getAliveUsers()} instead.
+ * @hide
+ */
+ @Deprecated
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+ boolean excludePreCreated) {
+ try {
+ return mService.getUsers(excludePartial, excludeDying, excludePreCreated);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user handles for all users on this device, based on the filtering parameters.
+ *
+ * @param excludeDying specify if the list should exclude users being removed.
+ * @return the list of user handles.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public @NonNull List<UserHandle> getUserHandles(boolean excludeDying) {
+ List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
+ /* excludePreCreated= */ true);
+ List<UserHandle> result = new ArrayList<>(users.size());
+ for (UserInfo user : users) {
+ result.add(user.getUserHandle());
+ }
+ return result;
+ }
+
+ /**
+ * 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(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public long[] getSerialNumbersOfUsers(boolean excludeDying) {
+ List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
+ /* excludePreCreated= */ true);
+ long[] result = new long[users.size()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = users.get(i).serialNumber;
+ }
+ return result;
+ }
+
+ /**
+ * @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 userId) {
+ try {
+ return mService.getUserAccount(userId);
+ } 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 userId, @Nullable String accountName) {
+ try {
+ mService.setUserAccount(userId, accountName);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns information for Primary user (which in practice is the same as the System user).
+ *
+ * @return the Primary user, null if not found.
+ * @deprecated For the system user, call {@link #getUserInfo} on {@link UserHandle#USER_SYSTEM},
+ * or just use {@link UserHandle#SYSTEM} or {@link UserHandle#USER_SYSTEM}.
+ * For the designated MainUser, use {@link #getMainUser()}.
+ *
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public @Nullable UserInfo getPrimaryUser() {
+ try {
+ return mService.getPrimaryUser();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user who was last in the foreground, not including the current user and not
+ * including profiles.
+ *
+ * <p>Returns {@code null} if there is no previous user, for example if there
+ * is only one full user (i.e. only one user which is not a profile) on the device.
+ *
+ * <p>This method may be used for example to find the user to switch back to if the
+ * current user is removed, or if creating a new user is aborted.
+ *
+ * <p>Note that reboots do not interrupt this calculation; the previous user need not have
+ * used the device since it rebooted.
+ *
+ * <p>Note also that on devices that support multiple users on multiple displays, it is possible
+ * that the returned user will be visible on a secondary display, as the foreground user is the
+ * one associated with the main display.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public @Nullable UserHandle getPreviousForegroundUser() {
+ try {
+ final int previousUser = mService.getPreviousFullUserToEnterForeground();
+ if (previousUser == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(previousUser);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks whether it's possible to add more users.
+ *
+ * @return true if more users can be added, false if limit has been reached.
+ *
+ * @deprecated use {@link #canAddMoreUsers(String)} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public boolean canAddMoreUsers() {
+ // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why
+ // not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles
+ final List<UserInfo> users = getAliveUsers();
+ 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 is possible to add more users of the given user type.
+ *
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}.
+ * @return true if more users of the given type can be added, otherwise false.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public boolean canAddMoreUsers(@NonNull String userType) {
+ try {
+ if (userType.equals(USER_TYPE_FULL_GUEST)) {
+ return mService.canAddMoreUsersOfType(userType);
+ } else {
+ return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the remaining number of users of the given type that can be created.
+ *
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}.
+ * @return how many additional users can be created.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public int getRemainingCreatableUserCount(@NonNull String userType) {
+ Objects.requireNonNull(userType, "userType must not be null");
+ try {
+ return mService.getRemainingCreatableUserCount(userType);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the remaining number of profiles that can be added to the context user.
+ * <p>Note that is applicable to any profile type (currently not including Restricted profiles).
+ *
+ * @param userType the type of profile, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return how many additional profiles can be created.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ @UserHandleAware
+ public int getRemainingCreatableProfileCount(@NonNull String userType) {
+ Objects.requireNonNull(userType, "userType must not be null");
+ try {
+ // TODO(b/142482943): Perhaps let the following code apply to restricted users too.
+ return mService.getRemainingCreatableProfileCount(userType, mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks whether it's possible to add more managed profiles.
+ * 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
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
+ try {
+ return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks whether it's possible to add more profiles of the given type to the given user.
+ *
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return true if more profiles can be added, false if limit has been reached.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public boolean canAddMoreProfilesToUser(@NonNull String userType, @UserIdInt int userId) {
+ try {
+ return mService.canAddMoreProfilesToUser(userType, userId, false);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks whether this device supports users of the given user type.
+ *
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}.
+ * @return true if the creation of users of the given user type is enabled on this device.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public boolean isUserTypeEnabled(@NonNull String userType) {
+ try {
+ return mService.isUserTypeEnabled(userType);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns list of the profiles of userId including userId itself.
+ * Note that this returns both enabled and not enabled profiles. See
+ * {@link #getEnabledProfiles(int)} if you need only the enabled ones.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#CREATE_USERS} or
+ * {@link android.Manifest.permission#QUERY_USERS} if userId is not the calling user.
+ * @param userId profiles of this user will be returned.
+ * @return the list of profiles.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ public List<UserInfo> getProfiles(@UserIdInt int userId) {
+ try {
+ return mService.getProfiles(userId, false /* enabledOnly */);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the 2 provided user handles belong to the same profile group.
+ *
+ * @param user one of the two user handles to check.
+ * @param otherUser one of the two user handles to check.
+ * @return true if the two users are in the same profile group.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ public boolean isSameProfileGroup(@NonNull UserHandle user, @NonNull UserHandle otherUser) {
+ return isSameProfileGroup(user.getIdentifier(), otherUser.getIdentifier());
+ }
+
+ /**
+ * Checks if the 2 provided user ids belong to the same profile group.
+ * @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
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS})
+ 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 userId including userId itself.
+ * Note that this returns only enabled.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#CREATE_USERS} or
+ * {@link android.Manifest.permission#QUERY_USERS} if userId is not the calling user.
+ * @param userId profiles of this user will be returned.
+ * @return the list of profiles.
+ * @deprecated use {@link #getUserProfiles()} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ public List<UserInfo> getEnabledProfiles(@UserIdInt int userId) {
+ try {
+ return mService.getProfiles(userId, true /* enabledOnly */);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a list of UserHandles for profiles associated with the context user, including the
+ * user itself.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * @return A non-empty list of UserHandles associated with the context user.
+ */
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ requiresAnyOfPermissionsIfNotCaller = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public List<UserHandle> getUserProfiles() {
+ int[] userIds = getProfileIds(getContextUserIfAppropriate(), true /* enabledOnly */);
+ return convertUserIdsToUserHandles(userIds);
+ }
+
+ /**
+ * Returns a list of ids for enabled profiles associated with the context user including the
+ * user itself.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * @return A non-empty list of UserHandles associated with the context user.
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public @NonNull List<UserHandle> getEnabledProfiles() {
+ return getProfiles(true);
+ }
+
+ /**
+ * Returns a list of ids for all profiles associated with the context user including the user
+ * itself.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * @return A non-empty list of UserHandles associated with the context user.
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public @NonNull List<UserHandle> getAllProfiles() {
+ return getProfiles(false);
+ }
+
+ /**
+ * Returns a list of ids for profiles associated with the context user including the user
+ * itself.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
+ * @return A non-empty list of UserHandles associated with the context user.
+ */
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ private @NonNull List<UserHandle> getProfiles(boolean enabledOnly) {
+ final int[] userIds = getProfileIds(mUserId, enabledOnly);
+ return convertUserIdsToUserHandles(userIds);
+ }
+
+ /** Converts the given array of userIds to a List of UserHandles. */
+ private @NonNull List<UserHandle> convertUserIdsToUserHandles(@NonNull int[] userIds) {
+ final 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.
+ * <p>Note that this includes all profile types (not including Restricted profiles).
+ *
+ * @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,
+ Manifest.permission.QUERY_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
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ public int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
+ return getProfileIds(userId, false /* enabledOnly */);
+ }
+
+ /**
+ * @see #getProfileIds(int, boolean)
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ 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 userId if called from a user that
+ * is not a profile.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ public int getCredentialOwnerProfile(@UserIdInt int userId) {
+ try {
+ return mService.getCredentialOwnerProfile(userId);
+ } 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
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ })
+ public UserInfo getProfileParent(@UserIdInt int userId) {
+ try {
+ return mService.getProfileParent(userId);
+ } 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(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_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)
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ Manifest.permission.MODIFY_QUIET_MODE}, conditional = true)
+ public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+ return requestQuietModeEnabled(enableQuietMode, userHandle, null);
+ }
+
+ /**
+ * Perform the same operation as {@link #requestQuietModeEnabled(boolean, UserHandle)}, but
+ * with a flag to tweak the behavior of the request.
+ *
+ * @param enableQuietMode whether quiet mode should be enabled or disabled
+ * @param userHandle user handle of the profile
+ * @param flags Can be 0 or {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED}.
+ * @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,
+ @QuietModeFlag int flags) {
+ return requestQuietModeEnabled(enableQuietMode, userHandle, null, flags);
+ }
+
+ /**
+ * 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
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean requestQuietModeEnabled(
+ boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
+ return requestQuietModeEnabled(enableQuietMode, userHandle, target, 0);
+ }
+
+ /**
+ * 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 #requestQuietModeEnabled(boolean, UserHandle)
+ * @hide
+ */
+ public boolean requestQuietModeEnabled(
+ boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target,
+ int flags) {
+ try {
+ return mService.requestQuietModeEnabled(
+ mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target,
+ flags);
+ } 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();
+ }
+ }
+
+ /**
+ * Returns whether the given user has a badge (generally to put on profiles' icons).
+ *
+ * @param userId userId of the user in question
+ * @return true if the user's icons should display a badge; false otherwise.
+ *
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ public boolean hasBadge(@UserIdInt int userId) {
+ if (!isProfile(userId)) {
+ // Since currently only profiles actually have badges, we can do this optimization.
+ return false;
+ }
+ try {
+ return mService.hasBadge(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the user associated with the context has a badge (generally to put on
+ * profiles' icons).
+ *
+ * @return true if the user's icons should display a badge; false otherwise.
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ @UserHandleAware
+ public boolean hasBadge() {
+ return hasBadge(mUserId);
+ }
+
+ /**
+ * Returns the light theme badge color for the given user (generally to color a profile's
+ * icon's badge).
+ *
+ * <p>To check whether a badge color is expected for the user, first call {@link #hasBadge}.
+ *
+ * @return the color (not the resource ID) to be used for the user's badge
+ * @throws Resources.NotFoundException if no valid badge color exists for this user
+ *
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ public @ColorInt int getUserBadgeColor(@UserIdInt int userId) {
+ try {
+ final int resourceId = mService.getUserBadgeColorResId(userId);
+ return Resources.getSystem().getColor(resourceId, null);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the dark theme badge color for the given user (generally to color a profile's icon's
+ * badge).
+ *
+ * <p>To check whether a badge color is expected for the user, first call {@link #hasBadge}.
+ *
+ * @return the color (not the resource ID) to be used for the user's badge
+ * @throws Resources.NotFoundException if no valid badge color exists for this user
+ *
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ public @ColorInt int getUserBadgeDarkColor(@UserIdInt int userId) {
+ try {
+ final int resourceId = mService.getUserBadgeDarkColorResId(userId);
+ return Resources.getSystem().getColor(resourceId, null);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the Resource ID of the user's icon badge.
+ *
+ * @return the Resource ID of the user's icon badge if it has one; otherwise
+ * {@link Resources#ID_NULL}.
+ *
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ public @DrawableRes int getUserIconBadgeResId(@UserIdInt int userId) {
+ try {
+ return mService.getUserIconBadgeResId(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the Resource ID of the user's badge.
+ *
+ * @return the Resource ID of the user's badge if it has one; otherwise
+ * {@link Resources#ID_NULL}.
+ *
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ public @DrawableRes int getUserBadgeResId(@UserIdInt int userId) {
+ try {
+ return mService.getUserBadgeResId(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the Resource ID of the user's badge without a background.
+ *
+ * @return the Resource ID of the user's no-background badge if it has one; otherwise
+ * {@link Resources#ID_NULL}.
+ *
+ * @see #getBadgedIconForUser more information about badging in general
+ * @hide
+ */
+ public @DrawableRes int getUserBadgeNoBackgroundResId(@UserIdInt int userId) {
+ try {
+ return mService.getUserBadgeNoBackgroundResId(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * If the target user is a profile of the calling user or the caller
+ * is itself a 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 profile of the calling user or the caller
+ * is itself a 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 profile of the calling user or the caller
+ * is itself a 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.
+ *
+ * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
+ * must be in the same profile group of specified user.
+ *
+ * @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) {
+ final int userId = user.getIdentifier();
+ if (!hasBadge(userId)) {
+ return label;
+ }
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ return dpm.getResources().getString(
+ getUpdatableUserBadgedLabelId(userId),
+ () -> getDefaultUserBadgedLabel(label, userId),
+ /* formatArgs= */ label);
+ }
+
+ private String getUpdatableUserBadgedLabelId(int userId) {
+ return isManagedProfile(userId) ? WORK_PROFILE_BADGED_LABEL : UNDEFINED;
+ }
+
+ private String getDefaultUserBadgedLabel(CharSequence label, int userId) {
+ try {
+ final int resourceId = mService.getUserBadgeLabelResId(userId);
+ return Resources.getSystem().getString(resourceId, label);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * If the user is a {@link UserManager#isProfile profile}, checks if the user
+ * shares media with its parent user (the user that created this profile).
+ * Returns false for any other type of user.
+ *
+ * @return true if the user shares media with its parent user, false otherwise.
+ *
+ * @deprecated use {@link #getUserProperties(UserHandle)} with
+ * {@link UserProperties#isMediaSharedWithParent()} instead.
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.QUERY_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS})
+ @SuppressAutoDoc
+ public boolean isMediaSharedWithParent() {
+ try {
+ return getUserProperties(UserHandle.of(mUserId)).isMediaSharedWithParent();
+ } catch (IllegalArgumentException e) {
+ // If the user doesn't exist, return false (for historical reasons)
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether the user can have shared lockscreen credential with its parent user.
+ *
+ * This API only works for {@link UserManager#isProfile() profiles}
+ * and will always return false for any other user type.
+ *
+ * @deprecated use {@link #getUserProperties(UserHandle)} with
+ * {@link UserProperties#isCredentialShareableWithParent()} instead.
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.QUERY_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS})
+ @SuppressAutoDoc
+ public boolean isCredentialSharableWithParent() {
+ try {
+ return getUserProperties(UserHandle.of(mUserId)).isCredentialShareableWithParent();
+ } catch (IllegalArgumentException e) {
+ // If the user doesn't exist, return false (for historical reasons)
+ return false;
+ }
+ }
+
+ /**
+ * Removes a user and its profiles along with their associated data.
+ * @param userId the integer handle of the user.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public boolean removeUser(@UserIdInt int userId) {
+ try {
+ return mService.removeUser(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a user and its profiles along with their 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(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_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
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
+ try {
+ return mService.removeUserEvenWhenDisallowed(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Immediately removes the user and its profiles or, if the user cannot be removed, such as
+ * when the user is the current user, then set the user as ephemeral
+ * so that it will be removed when it is stopped.
+ *
+ * @param overrideDevicePolicy when {@code true}, user is removed even if the caller has
+ * the {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction
+ *
+ * @return the {@link RemoveResult} code: {@link #REMOVE_RESULT_REMOVED},
+ * {@link #REMOVE_RESULT_DEFERRED}, {@link #REMOVE_RESULT_ALREADY_BEING_REMOVED},
+ * {@link #REMOVE_RESULT_ERROR_USER_RESTRICTION}, {@link #REMOVE_RESULT_ERROR_USER_NOT_FOUND},
+ * {@link #REMOVE_RESULT_ERROR_SYSTEM_USER},
+ * {@link #REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN}, or
+ * {@link #REMOVE_RESULT_ERROR_UNKNOWN}. All error codes have negative values.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public @RemoveResult int removeUserWhenPossible(@NonNull UserHandle user,
+ boolean overrideDevicePolicy) {
+ try {
+ return mService.removeUserWhenPossible(user.getIdentifier(), overrideDevicePolicy);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if {@link #removeUserWhenPossible} returned a success code which means that the user
+ * has been removed or is slated for removal.
+ *
+ * @param result is {@link #RemoveResult} code return by {@link #removeUserWhenPossible}.
+ * @return {@code true} if it is a success code.
+ * @hide
+ */
+ @SystemApi
+ public static boolean isRemoveResultSuccessful(@RemoveResult int result) {
+ return result >= 0;
+ }
+
+ /**
+ * Updates the user's name.
+ *
+ * @param userId the user's integer id
+ * @param name the new name for the user
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public void setUserName(@UserIdInt int userId, String name) {
+ try {
+ mService.setUserName(userId, name);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the user as ephemeral or non-ephemeral.
+ *
+ * If the user was initially created as ephemeral then this
+ * method has no effect and false is returned.
+ *
+ * @param userId the user's integer id
+ * @param enableEphemeral true: change user state to ephemeral,
+ * false: change user state to non-ephemeral
+ * @return true: user now has the desired ephemeral state,
+ * false: desired user ephemeral state could not be set
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
+ try {
+ return mService.setUserEphemeral(userId, enableEphemeral);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates the context user's name.
+ *
+ * @param name the new name for the user
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware
+ public void setUserName(@Nullable String name) {
+ setUserName(mUserId, name);
+ }
+
+ /**
+ * Sets the user's photo.
+ * @param userId the user for whom to change the photo.
+ * @param icon the bitmap to set as the photo.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon) {
+ try {
+ mService.setUserIcon(userId, icon);
+ } catch (ServiceSpecificException e) {
+ return;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the context user's photo.
+ *
+ * @param icon the bitmap to set as the photo.
+ *
+ * @throws UserOperationException according to the function signature, but may not actually
+ * throw it in practice. Catch RuntimeException instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @UserHandleAware
+ public void setUserIcon(@NonNull Bitmap icon) throws UserOperationException {
+ setUserIcon(mUserId, icon);
+ }
+
+ /**
+ * Returns a bitmap of the user's photo
+ * @param userId 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
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+ public Bitmap getUserIcon(@UserIdInt int userId) {
+ try {
+ ParcelFileDescriptor fd = mService.getUserIcon(userId);
+ 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 context user's photo.
+ *
+ * @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})
+ @UserHandleAware
+ public @Nullable Bitmap getUserIcon() {
+ return getUserIcon(mUserId);
+ }
+
+ /**
+ * 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;
+ return Math.max(1, SystemProperties.getInt("fw.max_users",
+ Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)));
+ }
+
+ /**
+ * Returns true if the user switcher is enabled (regardless of whether there is anything
+ * interesting for it to show).
+ *
+ * @return true if user switcher is enabled
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS // And INTERACT_ if diff profile group
+ })
+ @UserHandleAware
+ public boolean isUserSwitcherEnabled() {
+ return isUserSwitcherEnabled(true);
+ }
+
+ /**
+ * Returns true if the user switcher should be shown.
+ *
+ * @param showEvenIfNotActionable value to return if the feature is enabled but there is nothing
+ * actionable for the user to do anyway
+ * @return true if user switcher should be shown.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS // And INTERACT_ if diff profile group
+ })
+ @UserHandleAware
+ public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
+
+ try {
+ return mService.isUserSwitcherEnabled(showEvenIfNotActionable, mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 userId. User handles can be recycled
+ * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+ * @param userId
+ * @return a serial number associated with that user, or -1 if the userId is not valid.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int getUserSerialNumber(@UserIdInt int userId) {
+ try {
+ return mService.getUserSerialNumber(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a userId 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 userId 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 the context 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
+ *
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing apps on the device with the ability to set
+ * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+ * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+ * set by all managing apps, use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+ *
+ * @see DevicePolicyManager
+ */
+ @WorkerThread
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public Bundle getApplicationRestrictions(String packageName) {
+ try {
+ return mService.getApplicationRestrictionsForUser(packageName,
+ getContextUserIfAppropriate());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it is possible for there to be multiple managing apps on the device with the ability to set
+ * restrictions, e.g. an Enterprise Device Policy Controller (DPC) and a Supervision admin.
+ * This API will only to return the restrictions set by the DPCs. To retrieve restrictions
+ * set by all managing apps, use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} instead.
+ *
+ * @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.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public void setDefaultGuestRestrictions(Bundle restrictions) {
+ try {
+ mService.setDefaultGuestRestrictions(restrictions);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Gets the default guest restrictions.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public Bundle getDefaultGuestRestrictions() {
+ try {
+ return mService.getDefaultGuestRestrictions();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns creation time of the given user. The given user must be the calling user or
+ * a profile associated with it.
+ * @param userHandle user handle of the calling user or a 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();
+ }
+ }
+
+ /**
+ * 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
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean someUserHasSeedAccount(String accountName, String accountType) {
+ try {
+ return mService.someUserHasSeedAccount(accountName, accountType);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if any initialized or uninitialized user has the specific 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 account was found
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public boolean someUserHasAccount(
+ @NonNull String accountName, @NonNull String accountType) {
+ Objects.requireNonNull(accountName, "accountName must not be null");
+ Objects.requireNonNull(accountType, "accountType must not be null");
+
+ try {
+ return mService.someUserHasAccount(accountName, accountType);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the user who should be in the foreground when boot completes. This should be called
+ * during boot, and the provided user must be a full user (i.e. not a profile).
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ public void setBootUser(@NonNull UserHandle bootUser) {
+ try {
+ mService.setBootUser(bootUser.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user who should be in the foreground when boot completes.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS})
+ @SuppressWarnings("[AndroidFrameworkContextUserId]")
+ public @NonNull UserHandle getBootUser() {
+ try {
+ return UserHandle.of(mService.getBootUser());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
+ private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
+
+ private final PropertyInvalidatedCache<Integer, String> mProfileTypeCache =
+ new PropertyInvalidatedCache<Integer, String>(32, CACHE_KEY_STATIC_USER_PROPERTIES) {
+ @Override
+ public String recompute(Integer query) {
+ try {
+ // Will be null (and not cached) if invalid user; otherwise cache the type.
+ String profileType = mService.getProfileType(query);
+ if (profileType != null) profileType = profileType.intern();
+ return profileType;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ /** @hide */
+ public static final void invalidateStaticUserProperties() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STATIC_USER_PROPERTIES);
+ }
+
+ /* Cache key for UserProperties object. */
+ private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+
+ // TODO: It would be better to somehow have this as static, so that it can work cross-context.
+ private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
+ new PropertyInvalidatedCache<Integer, UserProperties>(16, CACHE_KEY_USER_PROPERTIES) {
+ @Override
+ public UserProperties recompute(Integer userId) {
+ try {
+ // If the userId doesn't exist, this will throw rather than cache garbage.
+ return mService.getUserPropertiesCopy(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ /** @hide */
+ public static final void invalidateUserPropertiesCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_PROPERTIES);
+ }
+
+ /**
+ * @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-34/android/os/VibrationAttributes.java b/android-34/android/os/VibrationAttributes.java
new file mode 100644
index 0000000..b2d62eb
--- /dev/null
+++ b/android-34/android/os/VibrationAttributes.java
@@ -0,0 +1,508 @@
+/*
+ * 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.annotation.Nullable;
+import android.annotation.TestApi;
+import android.media.AudioAttributes;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Encapsulates a collection of attributes describing information about a vibration.
+ */
+public final class VibrationAttributes implements Parcelable {
+ private static final String TAG = "VibrationAttributes";
+
+ /** @hide */
+ @IntDef(prefix = { "USAGE_CLASS_" }, value = {
+ USAGE_CLASS_UNKNOWN,
+ USAGE_CLASS_ALARM,
+ USAGE_CLASS_FEEDBACK,
+ USAGE_CLASS_MEDIA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageClass {}
+
+ /** @hide */
+ @IntDef(prefix = { "USAGE_" }, value = {
+ USAGE_UNKNOWN,
+ USAGE_ACCESSIBILITY,
+ USAGE_ALARM,
+ USAGE_COMMUNICATION_REQUEST,
+ USAGE_HARDWARE_FEEDBACK,
+ USAGE_MEDIA,
+ USAGE_NOTIFICATION,
+ USAGE_PHYSICAL_EMULATION,
+ USAGE_RINGTONE,
+ USAGE_TOUCH,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Usage {}
+
+ /**
+ * Vibration usage filter value to match all usages.
+ * @hide
+ */
+ public static final int USAGE_FILTER_MATCH_ALL = -1;
+ /**
+ * Vibration usage class value to use when the vibration usage class is unknown.
+ */
+ public static final int USAGE_CLASS_UNKNOWN = 0x0;
+ /**
+ * Vibration usage class value to use when the vibration is initiated to catch user's
+ * attention, such as alarm, ringtone, and notification vibrations.
+ */
+ public static final int USAGE_CLASS_ALARM = 0x1;
+ /**
+ * Vibration usage class value to use when the vibration is initiated as a response to user's
+ * actions, such as emulation of physical effects, and texting feedback vibration.
+ */
+ public static final int USAGE_CLASS_FEEDBACK = 0x2;
+ /**
+ * Vibration usage class value to use when the vibration is part of media, such as music, movie,
+ * soundtrack, game or animations.
+ */
+ public static final int USAGE_CLASS_MEDIA = 0x3;
+
+ /**
+ * Mask for vibration usage class value.
+ */
+ public static final int USAGE_CLASS_MASK = 0xF;
+
+ /**
+ * Usage value to use when usage is unknown.
+ */
+ public static final int USAGE_UNKNOWN = 0x0 | USAGE_CLASS_UNKNOWN;
+ /**
+ * Usage value to use for alarm vibrations.
+ */
+ public static final int USAGE_ALARM = 0x10 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for ringtone vibrations.
+ */
+ public static final int USAGE_RINGTONE = 0x20 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for notification vibrations.
+ */
+ public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for vibrations which mean a request to enter/end a
+ * communication with the user, such as a voice prompt.
+ */
+ public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for touch vibrations.
+ *
+ * <p>Most typical haptic feedback should be classed as <em>touch</em> feedback. Examples
+ * include vibrations for tap, long press, drag and scroll.
+ */
+ public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for vibrations which emulate physical hardware reactions,
+ * such as edge squeeze.
+ *
+ * <p>Note that normal screen-touch feedback "click" effects would typically be
+ * classed as {@link #USAGE_TOUCH}, and that on-screen "physical" animations
+ * like bouncing would be {@link #USAGE_MEDIA}.
+ */
+ public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for vibrations which provide a feedback for hardware
+ * component interaction, such as a fingerprint sensor.
+ */
+ public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for accessibility vibrations, such as with a screen reader.
+ */
+ public static final int USAGE_ACCESSIBILITY = 0x40 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for media vibrations, such as music, movie, soundtrack, animations, games,
+ * or any interactive media that isn't for touch feedback specifically.
+ */
+ public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "FLAG_" }, flag = true, value = {
+ FLAG_BYPASS_INTERRUPTION_POLICY,
+ FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
+ FLAG_INVALIDATE_SETTINGS_CACHE,
+ FLAG_PIPELINED_EFFECT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flag{}
+
+ /**
+ * Flag requesting vibration effect to be played even under limited interruptions.
+ *
+ * <p>Only privileged apps can ignore user settings that limit interruptions, and this
+ * flag will be ignored otherwise.
+ */
+ public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 1;
+
+ /**
+ * Flag requesting vibration effect to be played even when user settings are disabling it.
+ *
+ * <p>Flag introduced to represent
+ * {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and
+ * {@link AudioAttributes#FLAG_BYPASS_MUTE}.
+ *
+ * @hide
+ */
+ public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 1 << 1;
+
+ /**
+ * Flag requesting vibration effect to be played with fresh user settings values.
+ *
+ * <p>This flag is not protected by any permission, but vibrations that use it require an extra
+ * query of user vibration intensity settings, ringer mode and other controls that affect the
+ * vibration effect playback, which can increase the latency for the overall request.
+ *
+ * <p>This is intended to be used on scenarios where the user settings might have changed
+ * recently, and needs to be applied to this vibration, like settings controllers that preview
+ * newly set intensities to the user.
+ *
+ * @hide
+ */
+ public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 1 << 2;
+
+ /**
+ * Flag requesting that this vibration effect be pipelined with other vibration effects from the
+ * same package that also carry this flag.
+ *
+ * <p>Pipelined effects won't cancel a running pipelined effect, but will instead play after
+ * it completes. However, only one pipelined effect can be waiting at a time - so if an effect
+ * is already waiting (but not running), it will be cancelled in favor of a newer one.
+ *
+ * @hide
+ */
+ public static final int FLAG_PIPELINED_EFFECT = 1 << 3;
+
+ /**
+ * All flags supported by vibrator service, update it when adding new flag.
+ * @hide
+ */
+ public static final int FLAG_ALL_SUPPORTED =
+ FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+ | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT;
+
+ /** Creates a new {@link VibrationAttributes} instance with given usage. */
+ public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
+ return new VibrationAttributes.Builder().setUsage(usage).build();
+ }
+
+ private final int mUsage;
+ private final int mFlags;
+ private final int mOriginalAudioUsage;
+
+ private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
+ @Flag int flags) {
+ mUsage = usage;
+ mOriginalAudioUsage = audioUsage;
+ mFlags = flags & FLAG_ALL_SUPPORTED;
+ }
+
+ /**
+ * Return the vibration usage class.
+ */
+ @UsageClass
+ public int getUsageClass() {
+ return mUsage & USAGE_CLASS_MASK;
+ }
+
+ /**
+ * Return the vibration usage.
+ */
+ @Usage
+ public int getUsage() {
+ return mUsage;
+ }
+
+ /**
+ * Return the flags.
+ * @return a combined mask of all flags
+ */
+ @Flag
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Check whether a flag is set
+ * @return true if a flag is set and false otherwise
+ */
+ public boolean isFlagSet(@Flag int flag) {
+ return (mFlags & flag) > 0;
+ }
+
+ /**
+ * Return {@link AudioAttributes} usage equivalent to {@link #getUsage()}.
+ * @return one of {@link AudioAttributes#SDK_USAGES} that represents {@link #getUsage()}
+ * @hide
+ */
+ @TestApi
+ @AudioAttributes.AttributeUsage
+ public int getAudioUsage() {
+ if (mOriginalAudioUsage != AudioAttributes.USAGE_UNKNOWN) {
+ // Return same audio usage set in the Builder.
+ return mOriginalAudioUsage;
+ }
+ // Return correct audio usage based on the vibration usage set in the Builder.
+ switch (mUsage) {
+ case USAGE_NOTIFICATION:
+ return AudioAttributes.USAGE_NOTIFICATION;
+ case USAGE_COMMUNICATION_REQUEST:
+ return AudioAttributes.USAGE_VOICE_COMMUNICATION;
+ case USAGE_RINGTONE:
+ return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+ case USAGE_TOUCH:
+ return AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
+ case USAGE_ALARM:
+ return AudioAttributes.USAGE_ALARM;
+ case USAGE_ACCESSIBILITY:
+ return AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+ case USAGE_MEDIA:
+ return AudioAttributes.USAGE_MEDIA;
+ default:
+ return AudioAttributes.USAGE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mUsage);
+ dest.writeInt(mOriginalAudioUsage);
+ dest.writeInt(mFlags);
+ }
+
+ private VibrationAttributes(Parcel src) {
+ mUsage = src.readInt();
+ mOriginalAudioUsage = src.readInt();
+ mFlags = src.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<VibrationAttributes>
+ CREATOR = new Parcelable.Creator<VibrationAttributes>() {
+ public VibrationAttributes createFromParcel(Parcel p) {
+ return new VibrationAttributes(p);
+ }
+ public VibrationAttributes[] newArray(int size) {
+ return new VibrationAttributes[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ VibrationAttributes rhs = (VibrationAttributes) o;
+ return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage
+ && mFlags == rhs.mFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUsage, mOriginalAudioUsage, mFlags);
+ }
+
+ @Override
+ public String toString() {
+ return "VibrationAttributes:"
+ + " Usage=" + usageToString()
+ + " Audio Usage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
+ + " Flags=" + mFlags;
+ }
+
+ /** @hide */
+ public String usageToString() {
+ return usageToString(mUsage);
+ }
+
+ /** @hide */
+ public static String usageToString(@Usage int usage) {
+ switch (usage) {
+ case USAGE_UNKNOWN:
+ return "UNKNOWN";
+ case USAGE_ALARM:
+ return "ALARM";
+ case USAGE_ACCESSIBILITY:
+ return "ACCESSIBILITY";
+ case USAGE_RINGTONE:
+ return "RINGTONE";
+ case USAGE_NOTIFICATION:
+ return "NOTIFICATION";
+ case USAGE_COMMUNICATION_REQUEST:
+ return "COMMUNICATION_REQUEST";
+ case USAGE_MEDIA:
+ return "MEDIA";
+ case USAGE_TOUCH:
+ return "TOUCH";
+ case USAGE_PHYSICAL_EMULATION:
+ return "PHYSICAL_EMULATION";
+ case USAGE_HARDWARE_FEEDBACK:
+ return "HARDWARE_FEEDBACK";
+ default:
+ return "unknown usage " + usage;
+ }
+ }
+
+ /**
+ * Builder class for {@link VibrationAttributes} objects.
+ * By default, all information is set to UNKNOWN.
+ */
+ public static final class Builder {
+ private int mUsage = USAGE_UNKNOWN;
+ private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
+ private int mFlags = 0x0;
+
+ /**
+ * Constructs a new Builder with the defaults.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given VibrationAttributes.
+ */
+ public Builder(@Nullable VibrationAttributes vib) {
+ if (vib != null) {
+ mUsage = vib.mUsage;
+ mOriginalAudioUsage = vib.mOriginalAudioUsage;
+ mFlags = vib.mFlags;
+ }
+ }
+
+ /**
+ * Constructs a new Builder from AudioAttributes.
+ */
+ public Builder(@NonNull AudioAttributes audio) {
+ setUsage(audio);
+ setFlags(audio);
+ }
+
+ private void setUsage(@NonNull AudioAttributes audio) {
+ mOriginalAudioUsage = audio.getUsage();
+ switch (audio.getUsage()) {
+ case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ mUsage = USAGE_NOTIFICATION;
+ break;
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ case AudioAttributes.USAGE_ASSISTANT:
+ mUsage = USAGE_COMMUNICATION_REQUEST;
+ break;
+ case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+ mUsage = USAGE_RINGTONE;
+ break;
+ case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ mUsage = USAGE_ACCESSIBILITY;
+ break;
+ case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+ mUsage = USAGE_TOUCH;
+ break;
+ case AudioAttributes.USAGE_ALARM:
+ mUsage = USAGE_ALARM;
+ break;
+ case AudioAttributes.USAGE_MEDIA:
+ case AudioAttributes.USAGE_GAME:
+ mUsage = USAGE_MEDIA;
+ break;
+ default:
+ mUsage = USAGE_UNKNOWN;
+ }
+ }
+
+ private void setFlags(@NonNull AudioAttributes audio) {
+ if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
+ mFlags |= FLAG_BYPASS_INTERRUPTION_POLICY;
+ }
+ if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_MUTE) != 0) {
+ // Muted audio stream translates to vibration usage having the value
+ // Vibrator.VIBRATION_INTENSITY_OFF set in the user setting.
+ mFlags |= FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ }
+ }
+
+ /**
+ * Combines all of the attributes that have been set and returns a new
+ * {@link VibrationAttributes} object.
+ * @return a new {@link VibrationAttributes} object
+ */
+ public @NonNull VibrationAttributes build() {
+ VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags);
+ return ans;
+ }
+
+ /**
+ * Sets the attribute describing the type of the corresponding vibration.
+ * @param usage The type of usage for the vibration
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setUsage(@Usage int usage) {
+ mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
+ mUsage = usage;
+ return this;
+ }
+
+ /**
+ * Sets only the flags specified in the bitmask, leaving the other supported flag values
+ * unchanged in the builder.
+ *
+ * @param flags Combination of flags to be set.
+ * @param mask Bit range that should be changed.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setFlags(@Flag int flags, int mask) {
+ mask &= FLAG_ALL_SUPPORTED;
+ mFlags = (mFlags & ~mask) | (flags & mask);
+ return this;
+ }
+
+ /**
+ * Set all supported flags with given combination of flags, overriding any previous values
+ * set to this builder.
+ *
+ * @param flags combination of flags to be set.
+ * @return the same Builder instance.
+ *
+ * @hide
+ */
+ public @NonNull Builder setFlags(@Flag int flags) {
+ return setFlags(flags, FLAG_ALL_SUPPORTED);
+ }
+ }
+}
diff --git a/android-34/android/os/VibrationEffect.java b/android-34/android/os/VibrationEffect.java
new file mode 100644
index 0000000..4366c28
--- /dev/null
+++ b/android-34/android/os/VibrationEffect.java
@@ -0,0 +1,1569 @@
+/*
+ * 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.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+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.os.Vibrator;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+ // Stevens' coefficient to scale the perceived vibration intensity.
+ private static final float SCALE_GAMMA = 0.65f;
+ // If a vibration is playing for longer than 1s, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_DURATION = 1000;
+ // If a vibration is playing more than 3 constants, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE = 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. Use this effect as a baseline, as it's the most common type of click effect.
+ */
+ public static final int EFFECT_CLICK = Effect.CLICK;
+
+ /**
+ * A double click effect.
+ */
+ public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+ /**
+ * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
+ */
+ public static final int EFFECT_TICK = Effect.TICK;
+
+ /**
+ * A thud effect.
+ * @see #get(int)
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final int EFFECT_THUD = Effect.THUD;
+
+ /**
+ * A pop effect.
+ * @see #get(int)
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final int EFFECT_POP = Effect.POP;
+
+ /**
+ * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
+ */
+ public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
+
+ /**
+ * A texture effect meant to replicate soft ticks.
+ *
+ * <p>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
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @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.
+ *
+ * <p>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) {
+ if (amplitude == 0) {
+ throw new IllegalArgumentException(
+ "amplitude must either be DEFAULT_AMPLITUDE, "
+ + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")");
+ }
+ return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */);
+ }
+
+ /**
+ * Create a waveform vibration, using only off/on transitions at the provided time intervals,
+ * and potentially repeating.
+ *
+ * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning
+ * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then
+ * the number of milliseconds turned off, and so on. Consequently, the first timing value will
+ * often be 0, so that the effect will start vibrating immediately.
+ *
+ * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with
+ * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE},
+ * beginning with 0.
+ *
+ * <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. Repeating effects will be played indefinitely
+ * and should be cancelled via {@link Vibrator#cancel()}.
+ *
+ * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and
+ * representing the length of time to sustain the individual item (not
+ * cumulative).
+ * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+ * want to repeat indefinitely.
+ *
+ * @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);
+ }
+
+ /**
+ * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
+ * vibration components) that is equivalent to this VibrationEffect.
+ *
+ * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
+ * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
+ * created with other means becomes converted into an equivalent legacy vibration pattern, even
+ * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
+ * vibration pattern for such effects, it will return {@code null}.
+ *
+ * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
+ * form of repeating behavior, regardless of how the effect was created. For repeating effects,
+ * the method will always return {@code null}.
+ *
+ * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
+ * the method successfully derived a vibration pattern equivalent to the effect
+ * (this will always be the case if the effect was created via
+ * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+ * {@code null}.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
+
+ /**
+ * Create a waveform vibration.
+ *
+ * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
+ * provided in separate arrays. 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, in milliseconds.
+ *
+ * <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. Repeating effects will be played indefinitely
+ * and should be cancelled via {@link Vibrator#cancel()}.
+ *
+ * @param timings The timing values, in milliseconds, 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 don't
+ * want to repeat indefinitely.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ if (timings.length != amplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length"
+ + " (timings.length=" + timings.length
+ + ", amplitudes.length=" + amplitudes.length + ")");
+ }
+ List<StepSegment> segments = new ArrayList<>();
+ for (int i = 0; i < timings.length; i++) {
+ float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE
+ ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE;
+ segments.add(new StepSegment(parsedAmplitude, /* frequencyHz= */ 0, (int) timings[i]));
+ }
+ VibrationEffect effect = new Composed(segments, repeat);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Create a predefined vibration effect.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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 Composed(
+ new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM));
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Get a predefined vibration effect associated with a given URI.
+ *
+ * <p>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) {
+ String[] uris = context.getResources().getStringArray(
+ com.android.internal.R.array.config_ringtoneEffectUris);
+
+ // Skip doing any IPC if we don't have any effects configured.
+ if (uris.length == 0) {
+ return null;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * Start composing a haptic effect.
+ *
+ * @see VibrationEffect.Composition
+ */
+ @NonNull
+ public static Composition startComposition() {
+ return new Composition();
+ }
+
+ /**
+ * Start building a waveform vibration.
+ *
+ * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+ * control over vibration amplitude and frequency via smooth transitions between values.
+ *
+ * <p>The waveform will start the first transition from the vibrator off state, with the
+ * resonant frequency by default. To provide an initial state, use
+ * {@link #startWaveform(VibrationEffect.VibrationParameter)}.
+ *
+ * @see VibrationEffect.WaveformBuilder
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static WaveformBuilder startWaveform() {
+ return new WaveformBuilder();
+ }
+
+ /**
+ * Start building a waveform vibration with an initial state specified by a
+ * {@link VibrationEffect.VibrationParameter}.
+ *
+ * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+ * control over vibration amplitude and frequency via smooth transitions between values.
+ *
+ * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration.
+ * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ *
+ * @see VibrationEffect.WaveformBuilder
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
+ WaveformBuilder builder = startWaveform();
+ builder.addTransition(Duration.ZERO, initialParameter);
+ return builder;
+ }
+
+ /**
+ * Start building a waveform vibration with an initial state specified by two
+ * {@link VibrationEffect.VibrationParameter VibrationParameters}.
+ *
+ * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
+ * control over vibration amplitude and frequency via smooth transitions between values.
+ *
+ * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration.
+ * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration, must be a different type
+ * of parameter than the one specified by the first argument.
+ * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ *
+ * @see VibrationEffect.WaveformBuilder
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
+ @NonNull VibrationParameter initialParameter2) {
+ WaveformBuilder builder = startWaveform();
+ builder.addTransition(Duration.ZERO, initialParameter1, initialParameter2);
+ return builder;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /**
+ * Gets the estimated duration of the vibration in milliseconds.
+ *
+ * <p>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();
+
+ /**
+ * Checks if a given {@link Vibrator} can play this effect as intended.
+ *
+ * <p>See @link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information
+ * about what counts as supported by a vibrator, and what counts as not.
+ *
+ * @hide
+ */
+ public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+
+ /**
+ * Returns true if this effect could represent a touch haptic feedback.
+ *
+ * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified
+ * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN,
+ * then this method will be used to classify the most common use case and make sure they are
+ * covered by the user settings for "Touch feedback".
+ *
+ * @hide
+ */
+ public boolean isHapticFeedbackCandidate() {
+ return false;
+ }
+
+ /**
+ * Resolve default values into integer amplitude numbers.
+ *
+ * @param defaultAmplitude the default amplitude to apply, must be between 0 and
+ * MAX_AMPLITUDE
+ * @return this if amplitude value is already set, or a copy of this effect with given default
+ * amplitude otherwise
+ *
+ * @hide
+ */
+ public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude);
+
+ /**
+ * Scale the vibration effect intensity with the given constraints.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ * @return this if there is no scaling to be done, or a copy of this effect with scaled
+ * vibration intensity otherwise
+ *
+ * @hide
+ */
+ public abstract <T extends VibrationEffect> T scale(float scaleFactor);
+
+ /**
+ * Applies given effect strength to prebaked effects represented by one of
+ * VibrationEffect.EFFECT_*.
+ *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ * @return this if there is no change to this effect, or a copy of this effect with applied
+ * effect strength otherwise.
+ * @hide
+ */
+ public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) {
+ return (T) this;
+ }
+
+ /**
+ * Scale given vibration intensity by the given factor.
+ *
+ * @param intensity relative intensity of the effect, must be between 0 and 1
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ * @hide
+ */
+ public static float scale(float intensity, float scaleFactor) {
+ // Applying gamma correction to the scale factor, which is the same as encoding the input
+ // value, scaling it, then decoding the scaled value.
+ float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
+
+ if (scaleFactor <= 1) {
+ // Scale down is simply a gamma corrected application of scaleFactor to the intensity.
+ // Scale up requires a different curve to ensure the intensity will not become > 1.
+ return intensity * scale;
+ }
+
+ // Apply the scale factor a few more times to make the ramp curve closer to the raw scale.
+ float extraScale = MathUtils.pow(scaleFactor, 4f - scaleFactor);
+ float x = intensity * scale * extraScale;
+ float maxX = scale * extraScale; // scaled x for intensity == 1
+
+ float expX = MathUtils.exp(x);
+ float expMaxX = MathUtils.exp(maxX);
+
+ // Using f = tanh as the scale up function so the max value will converge.
+ // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1).
+ float a = (expMaxX + 1f) / (expMaxX - 1f);
+ float fx = (expX - 1f) / (expX + 1f);
+
+ return MathUtils.constrain(a * fx, 0f, 1f);
+ }
+
+ /** @hide */
+ public static String effectIdToString(int effectId) {
+ switch (effectId) {
+ case EFFECT_CLICK:
+ return "CLICK";
+ case EFFECT_TICK:
+ return "TICK";
+ case EFFECT_HEAVY_CLICK:
+ return "HEAVY_CLICK";
+ case EFFECT_DOUBLE_CLICK:
+ return "DOUBLE_CLICK";
+ case EFFECT_POP:
+ return "POP";
+ case EFFECT_THUD:
+ return "THUD";
+ case EFFECT_TEXTURE_TICK:
+ return "TEXTURE_TICK";
+ default:
+ return Integer.toString(effectId);
+ }
+ }
+
+ /** @hide */
+ public static String effectStrengthToString(int effectStrength) {
+ switch (effectStrength) {
+ case EFFECT_STRENGTH_LIGHT:
+ return "LIGHT";
+ case EFFECT_STRENGTH_MEDIUM:
+ return "MEDIUM";
+ case EFFECT_STRENGTH_STRONG:
+ return "STRONG";
+ default:
+ return Integer.toString(effectStrength);
+ }
+ }
+
+ /**
+ * Implementation of {@link VibrationEffect} described by a composition of one or more
+ * {@link VibrationEffectSegment}, with an optional index to represent repeating effects.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Composed extends VibrationEffect {
+ private final ArrayList<VibrationEffectSegment> mSegments;
+ private final int mRepeatIndex;
+
+ Composed(@NonNull Parcel in) {
+ this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt());
+ }
+
+ Composed(@NonNull VibrationEffectSegment segment) {
+ this(Arrays.asList(segment), /* repeatIndex= */ -1);
+ }
+
+ /** @hide */
+ public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) {
+ super();
+ mSegments = new ArrayList<>(segments);
+ mRepeatIndex = repeatIndex;
+ }
+
+ @NonNull
+ public List<VibrationEffectSegment> getSegments() {
+ return mSegments;
+ }
+
+ public int getRepeatIndex() {
+ return mRepeatIndex;
+ }
+
+ /** @hide */
+ @Override
+ @Nullable
+ public long[] computeCreateWaveformOffOnTimingsOrNull() {
+ if (getRepeatIndex() >= 0) {
+ // Repeating effects cannot be fully represented as a long[] legacy pattern.
+ return null;
+ }
+
+ List<VibrationEffectSegment> segments = getSegments();
+
+ // The maximum possible size of the final pattern is 1 plus the number of segments in
+ // the original effect. This is because we will add an empty "off" segment at the
+ // start of the pattern if the first segment of the original effect is an "on" segment.
+ // (because the legacy patterns start with an "off" pattern). Other than this one case,
+ // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
+ // that are all "on" or "off") and create a pattern entry for the total duration, which
+ // will not take more number pattern entries than the number of segments processed.
+ long[] patternBuffer = new long[segments.size() + 1];
+ int patternIndex = 0;
+
+ for (int i = 0; i < segments.size(); i++) {
+ StepSegment stepSegment =
+ castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
+ if (stepSegment == null) {
+ // This means that there is 1 or more segments of this effect that is/are not a
+ // possible component of a legacy vibration pattern. Thus, the VibrationEffect
+ // does not have any equivalent legacy vibration pattern.
+ return null;
+ }
+
+ boolean isSegmentOff = stepSegment.getAmplitude() == 0;
+ // Even pattern indices are "off", and odd pattern indices are "on"
+ boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
+ if (isSegmentOff != isCurrentPatternIndexOff) {
+ // Move the pattern index one step ahead, so that the current segment's
+ // "off"/"on" property matches that of the index's
+ ++patternIndex;
+ }
+ patternBuffer[patternIndex] += stepSegment.getDuration();
+ }
+
+ return Arrays.copyOf(patternBuffer, patternIndex + 1);
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ int segmentCount = mSegments.size();
+ boolean hasNonZeroDuration = false;
+ for (int i = 0; i < segmentCount; i++) {
+ VibrationEffectSegment segment = mSegments.get(i);
+ segment.validate();
+ // A segment with unknown duration = -1 still counts as a non-zero duration.
+ hasNonZeroDuration |= segment.getDuration() != 0;
+ }
+ if (!hasNonZeroDuration) {
+ throw new IllegalArgumentException("at least one timing must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ if (mRepeatIndex != -1) {
+ Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1,
+ "repeat index must be within the bounds of the segments (segments.length="
+ + segmentCount + ", index=" + mRepeatIndex + ")");
+ }
+ }
+
+ @Override
+ public long getDuration() {
+ if (mRepeatIndex >= 0) {
+ return Long.MAX_VALUE;
+ }
+ int segmentCount = mSegments.size();
+ long totalDuration = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ long segmentDuration = mSegments.get(i).getDuration();
+ if (segmentDuration < 0) {
+ return segmentDuration;
+ }
+ totalDuration += segmentDuration;
+ }
+ return totalDuration;
+ }
+
+ /** @hide */
+ @Override
+ public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ for (VibrationEffectSegment segment : mSegments) {
+ if (!segment.areVibrationFeaturesSupported(vibrator)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ long totalDuration = getDuration();
+ if (totalDuration > MAX_HAPTIC_FEEDBACK_DURATION) {
+ // Vibration duration is known and is longer than the max duration used to classify
+ // haptic feedbacks (or repeating indefinitely with duration == Long.MAX_VALUE).
+ return false;
+ }
+ int segmentCount = mSegments.size();
+ if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) {
+ // Vibration has some prebaked or primitive constants, it should be limited to the
+ // max composition size used to classify haptic feedbacks.
+ return false;
+ }
+ totalDuration = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ if (!mSegments.get(i).isHapticFeedbackCandidate()) {
+ // There is at least one segment that is not a candidate for a haptic feedback.
+ return false;
+ }
+ long segmentDuration = mSegments.get(i).getDuration();
+ if (segmentDuration > 0) {
+ totalDuration += segmentDuration;
+ }
+ }
+ // Vibration might still have some ramp or step segments, check the known duration.
+ return totalDuration <= MAX_HAPTIC_FEEDBACK_DURATION;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public Composed resolve(int defaultAmplitude) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
+ }
+ if (resolvedSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+ resolved.validate();
+ return resolved;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public Composed scale(float scaleFactor) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+ }
+ if (scaledSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public Composed applyEffectStrength(int effectStrength) {
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength));
+ }
+ if (scaledSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof Composed)) {
+ return false;
+ }
+ Composed other = (Composed) o;
+ return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSegments, mRepeatIndex);
+ }
+
+ @Override
+ public String toString() {
+ return "Composed{segments=" + mSegments
+ + ", repeat=" + mRepeatIndex
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeList(mSegments);
+ out.writeInt(mRepeatIndex);
+ }
+
+ @NonNull
+ public static final Creator<Composed> CREATOR =
+ new Creator<Composed>() {
+ @Override
+ public Composed createFromParcel(Parcel in) {
+ return new Composed(in);
+ }
+
+ @Override
+ public Composed[] newArray(int size) {
+ return new Composed[size];
+ }
+ };
+
+ /**
+ * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
+ * only if it can possibly be a segment for an effect created via
+ * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+ */
+ @Nullable
+ private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
+ VibrationEffectSegment segment) {
+ if (!(segment instanceof StepSegment)) {
+ return null;
+ }
+
+ StepSegment stepSegment = (StepSegment) segment;
+ if (stepSegment.getFrequencyHz() != 0) {
+ return null;
+ }
+
+ float amplitude = stepSegment.getAmplitude();
+ if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
+ return null;
+ }
+
+ return stepSegment;
+ }
+ }
+
+ /**
+ * A composition of haptic elements that are combined to be playable as a single
+ * {@link VibrationEffect}.
+ *
+ * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and
+ * can be added to a composition to create a custom vibration effect. Here is an example of an
+ * effect that grows in intensity and then dies off, with a longer rising portion for emphasis
+ * and an extra tick 100ms after:
+ *
+ * <pre>
+ * {@code VibrationEffect effect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+ * .compose();}</pre>
+ *
+ * <p>When choosing to play a composed effect, you should check that individual components are
+ * supported by the device by using {@link Vibrator#arePrimitivesSupported}.
+ *
+ * @see VibrationEffect#startComposition()
+ */
+ public static final class Composition {
+ /** @hide */
+ @IntDef(prefix = { "PRIMITIVE_" }, value = {
+ PRIMITIVE_CLICK,
+ PRIMITIVE_THUD,
+ PRIMITIVE_SPIN,
+ PRIMITIVE_QUICK_RISE,
+ PRIMITIVE_SLOW_RISE,
+ PRIMITIVE_QUICK_FALL,
+ PRIMITIVE_TICK,
+ PRIMITIVE_LOW_TICK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrimitiveType {
+ }
+
+ /**
+ * Exception thrown when adding an element to a {@link Composition} that already ends in an
+ * indefinitely repeating effect.
+ * @hide
+ */
+ @TestApi
+ public static final class UnreachableAfterRepeatingIndefinitelyException
+ extends IllegalStateException {
+ UnreachableAfterRepeatingIndefinitelyException() {
+ super("Compositions ending in an indefinitely repeating effect can't be extended");
+ }
+ }
+
+ /**
+ * No haptic effect. Used to generate extended delays between primitives.
+ *
+ * @hide
+ */
+ public static final int PRIMITIVE_NOOP = 0;
+ /**
+ * This effect should produce a sharp, crisp click sensation.
+ */
+ public static final int PRIMITIVE_CLICK = 1;
+ /**
+ * A haptic effect that simulates downwards movement with gravity. Often
+ * followed by extra energy of hitting and reverberation to augment
+ * physicality.
+ */
+ public static final int PRIMITIVE_THUD = 2;
+ /**
+ * A haptic effect that simulates spinning momentum.
+ */
+ public static final int PRIMITIVE_SPIN = 3;
+ /**
+ * A haptic effect that simulates quick upward movement against gravity.
+ */
+ public static final int PRIMITIVE_QUICK_RISE = 4;
+ /**
+ * A haptic effect that simulates slow upward movement against gravity.
+ */
+ public static final int PRIMITIVE_SLOW_RISE = 5;
+ /**
+ * A haptic effect that simulates quick downwards movement with gravity.
+ */
+ public static final int PRIMITIVE_QUICK_FALL = 6;
+ /**
+ * This very short effect should produce a light crisp sensation intended
+ * to be used repetitively for dynamic feedback.
+ */
+ // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK
+ public static final int PRIMITIVE_TICK = 7;
+ /**
+ * This very short low frequency effect should produce a light crisp sensation
+ * intended to be used repetitively for dynamic feedback.
+ */
+ // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK
+ public static final int PRIMITIVE_LOW_TICK = 8;
+
+
+ private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+ private int mRepeatIndex = -1;
+
+ Composition() {}
+
+ /**
+ * Adds a time duration to the current composition, during which the vibrator will be
+ * turned off.
+ *
+ * @param duration The length of time the vibrator should be off. Value must be non-negative
+ * and will be truncated to milliseconds.
+ * @return This {@link Composition} object to enable adding multiple elements in one chain.
+ *
+ * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+ * ending with a repeating effect.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Composition addOffDuration(@NonNull Duration duration) {
+ int durationMs = (int) duration.toMillis();
+ Preconditions.checkArgumentNonnegative(durationMs, "Off period must be non-negative");
+ if (durationMs > 0) {
+ // Created a segment sustaining the zero amplitude to represent the delay.
+ addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0,
+ (int) duration.toMillis()));
+ }
+ return this;
+ }
+
+ /**
+ * Add a haptic effect to the end of the current composition.
+ *
+ * <p>If this effect is repeating (e.g. created by {@link VibrationEffect#createWaveform}
+ * with a non-negative repeat index, or created by another composition that has effects
+ * repeating indefinitely), then no more effects or primitives will be accepted by this
+ * composition after this method. Such effects should be cancelled via
+ * {@link Vibrator#cancel()}.
+ *
+ * @param effect The effect to add to the end of this composition.
+ * @return This {@link Composition} object to enable adding multiple elements in one chain.
+ *
+ * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+ * ending with a repeating effect.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Composition addEffect(@NonNull VibrationEffect effect) {
+ return addSegments(effect);
+ }
+
+ /**
+ * Add a haptic effect to the end of the current composition and play it on repeat,
+ * indefinitely.
+ *
+ * <p>The entire effect will be played on repeat, indefinitely, after all other elements
+ * already added to this composition are played. No more effects or primitives will be
+ * accepted by this composition after this method. Such effects should be cancelled via
+ * {@link Vibrator#cancel()}.
+ *
+ * @param effect The effect to add to the end of this composition, must be finite.
+ * @return This {@link Composition} object to enable adding multiple elements in one chain,
+ * although only {@link #compose()} can follow this call.
+ *
+ * @throws IllegalArgumentException if the given effect is already repeating indefinitely.
+ * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
+ * ending with a repeating effect.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Composition repeatEffectIndefinitely(@NonNull VibrationEffect effect) {
+ Preconditions.checkArgument(effect.getDuration() < Long.MAX_VALUE,
+ "Can't repeat an indefinitely repeating effect. Consider addEffect instead.");
+ int previousSegmentCount = mSegments.size();
+ addSegments(effect);
+ // Set repeat after segments were added, since addSegments checks this index.
+ mRepeatIndex = previousSegmentCount;
+ return this;
+ }
+
+ /**
+ * Add a haptic primitive to the end of the current composition.
+ *
+ * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
+ * default scale applied.
+ *
+ * @param primitiveId The primitive to add
+ * @return This {@link Composition} object to enable adding multiple elements in one chain.
+ */
+ @NonNull
+ public Composition addPrimitive(@PrimitiveType int primitiveId) {
+ return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
+ }
+
+ /**
+ * Add a haptic primitive to the end of the current composition.
+ *
+ * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
+ *
+ * @param primitiveId The primitive to add
+ * @param scale The scale to apply to the intensity of the primitive.
+ * @return This {@link Composition} object to enable adding multiple elements in one chain.
+ */
+ @NonNull
+ public Composition addPrimitive(@PrimitiveType int primitiveId,
+ @FloatRange(from = 0f, to = 1f) float scale) {
+ return addPrimitive(primitiveId, scale, /*delay*/ 0);
+ }
+
+ /**
+ * Add a haptic primitive to the end of the current composition.
+ *
+ * @param primitiveId The primitive to add
+ * @param scale The scale to apply to the intensity of the primitive.
+ * @param delay The amount of time in milliseconds to wait before playing this primitive,
+ * starting at the time the previous element in this composition is finished.
+ * @return This {@link Composition} object to enable adding multiple elements in one chain.
+ */
+ @NonNull
+ public Composition addPrimitive(@PrimitiveType int primitiveId,
+ @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
+ PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale,
+ delay);
+ primitive.validate();
+ return addSegment(primitive);
+ }
+
+ private Composition addSegment(VibrationEffectSegment segment) {
+ if (mRepeatIndex >= 0) {
+ throw new UnreachableAfterRepeatingIndefinitelyException();
+ }
+ mSegments.add(segment);
+ return this;
+ }
+
+ private Composition addSegments(VibrationEffect effect) {
+ if (mRepeatIndex >= 0) {
+ throw new UnreachableAfterRepeatingIndefinitelyException();
+ }
+ Composed composed = (Composed) effect;
+ if (composed.getRepeatIndex() >= 0) {
+ // Start repeating from the index relative to the composed waveform.
+ mRepeatIndex = mSegments.size() + composed.getRepeatIndex();
+ }
+ mSegments.addAll(composed.getSegments());
+ return this;
+ }
+
+ /**
+ * Compose all of the added primitives together into a single {@link VibrationEffect}.
+ *
+ * <p>The {@link Composition} object is still valid after this call, so you can continue
+ * adding more primitives to it and generating more {@link VibrationEffect}s by calling this
+ * method again.
+ *
+ * @return The {@link VibrationEffect} resulting from the composition of the primitives.
+ */
+ @NonNull
+ public VibrationEffect compose() {
+ if (mSegments.isEmpty()) {
+ throw new IllegalStateException(
+ "Composition must have at least one element to compose.");
+ }
+ VibrationEffect effect = new Composed(mSegments, mRepeatIndex);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Convert the primitive ID to a human readable string for debugging.
+ * @param id The ID to convert
+ * @return The ID in a human readable format.
+ * @hide
+ */
+ public static String primitiveToString(@PrimitiveType int id) {
+ switch (id) {
+ case PRIMITIVE_NOOP:
+ return "PRIMITIVE_NOOP";
+ case PRIMITIVE_CLICK:
+ return "PRIMITIVE_CLICK";
+ case PRIMITIVE_THUD:
+ return "PRIMITIVE_THUD";
+ case PRIMITIVE_SPIN:
+ return "PRIMITIVE_SPIN";
+ case PRIMITIVE_QUICK_RISE:
+ return "PRIMITIVE_QUICK_RISE";
+ case PRIMITIVE_SLOW_RISE:
+ return "PRIMITIVE_SLOW_RISE";
+ case PRIMITIVE_QUICK_FALL:
+ return "PRIMITIVE_QUICK_FALL";
+ case PRIMITIVE_TICK:
+ return "PRIMITIVE_TICK";
+ case PRIMITIVE_LOW_TICK:
+ return "PRIMITIVE_LOW_TICK";
+ default:
+ return Integer.toString(id);
+ }
+ }
+ }
+
+ /**
+ * A builder for waveform haptic effects.
+ *
+ * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
+ * parameters. These parameters can be the vibration amplitude, frequency, or both.
+ *
+ * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
+ * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
+ *
+ * <pre>
+ * {@code import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+ * import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+ *
+ * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+ * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+ * .addSustain(Duration.ofMillis(200))
+ * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+ * .build();}</pre>
+ *
+ * <p>The initial state of the waveform can be set via
+ * {@link VibrationEffect#startWaveform(VibrationParameter)} or
+ * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial
+ * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off,
+ * represented by zero amplitude, at the vibrator's resonant frequency.
+ *
+ * <p>Repeating waveforms can be created by building the repeating block separately and adding
+ * it to the end of a composition with
+ * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}:
+ *
+ * <p>Note that physical vibration actuators have different reaction times for changing
+ * amplitude and frequency. Durations specified here represent a timeline for the target
+ * parameters, and quality of effects may be improved if the durations allow time for a
+ * transition to be smoothly applied.
+ *
+ * <p>The following example illustrates both an initial state and a repeating section, using
+ * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
+ * repeated beating effect with a rise that stretches out and a sharp finish.
+ *
+ * <pre>
+ * {@code VibrationEffect patternToRepeat = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * .addSustain(Duration.ofMillis(10))
+ * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+ * .addSustain(Duration.ofMillis(30))
+ * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+ * .addSustain(Duration.ofMillis(50))
+ * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+ * .build();
+ *
+ * VibrationEffect effect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ * .addOffDuration(Duration.ofMillis(20))
+ * .repeatEffectIndefinitely(patternToRepeat)
+ * .compose();}</pre>
+ *
+ * <p>The amplitude step waveforms that can be created via
+ * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
+ * {@link WaveformBuilder} by adding zero duration transitions:
+ *
+ * <pre>
+ * {@code // These two effects are the same
+ * VibrationEffect waveform = VibrationEffect.createWaveform(
+ * new long[] { 10, 20, 30 }, // timings in milliseconds
+ * new int[] { 51, 102, 204 }, // amplitudes in [0,255]
+ * -1); // repeat index
+ *
+ * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * .addSustain(Duration.ofMillis(10))
+ * .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+ * .addSustain(Duration.ofMillis(20))
+ * .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+ * .addSustain(Duration.ofMillis(30))
+ * .build();}</pre>
+ *
+ * @see VibrationEffect#startWaveform
+ * @hide
+ */
+ @TestApi
+ public static final class WaveformBuilder {
+ // Epsilon used for float comparison of amplitude and frequency values on transitions.
+ private static final float EPSILON = 1e-5f;
+
+ private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+ private float mLastAmplitude = 0f;
+ private float mLastFrequencyHz = 0f;
+
+ WaveformBuilder() {}
+
+ /**
+ * Add a transition to new vibration parameter value to the end of this waveform.
+ *
+ * <p>The duration represents how long the vibrator should take to smoothly transition to
+ * the new vibration parameter. If the duration is zero then the vibrator will jump to the
+ * new value as fast as possible.
+ *
+ * <p>Vibration parameter values will be truncated to conform to the device capabilities
+ * according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
+ *
+ * @param duration The length of time this transition should take. Value must be
+ * non-negative and will be truncated to milliseconds.
+ * @param targetParameter The new target {@link VibrationParameter} value to be reached
+ * after the given duration.
+ * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
+ * chain.
+ * @hide
+ */
+ @TestApi
+ @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
+ @NonNull
+ public WaveformBuilder addTransition(@NonNull Duration duration,
+ @NonNull VibrationParameter targetParameter) {
+ Preconditions.checkNotNull(duration, "Duration is null");
+ checkVibrationParameter(targetParameter, "targetParameter");
+ float amplitude = extractTargetAmplitude(targetParameter, /* target2= */ null);
+ float frequencyHz = extractTargetFrequency(targetParameter, /* target2= */ null);
+ addTransitionSegment(duration, amplitude, frequencyHz);
+ return this;
+ }
+
+ /**
+ * Add a transition to new vibration parameters to the end of this waveform.
+ *
+ * <p>The duration represents how long the vibrator should take to smoothly transition to
+ * the new vibration parameters. If the duration is zero then the vibrator will jump to the
+ * new values as fast as possible.
+ *
+ * <p>Vibration parameters values will be truncated to conform to the device capabilities
+ * according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
+ *
+ * @param duration The length of time this transition should take. Value must be
+ * non-negative and will be truncated to milliseconds.
+ * @param targetParameter1 The first target {@link VibrationParameter} value to be reached
+ * after the given duration.
+ * @param targetParameter2 The second target {@link VibrationParameter} value to be reached
+ * after the given duration, must be a different type of parameter
+ * than the one specified by the first argument.
+ * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
+ * chain.
+ * @hide
+ */
+ @TestApi
+ @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
+ @NonNull
+ public WaveformBuilder addTransition(@NonNull Duration duration,
+ @NonNull VibrationParameter targetParameter1,
+ @NonNull VibrationParameter targetParameter2) {
+ Preconditions.checkNotNull(duration, "Duration is null");
+ checkVibrationParameter(targetParameter1, "targetParameter1");
+ checkVibrationParameter(targetParameter2, "targetParameter2");
+ Preconditions.checkArgument(
+ !Objects.equals(targetParameter1.getClass(), targetParameter2.getClass()),
+ "Parameter arguments must specify different parameter types");
+ float amplitude = extractTargetAmplitude(targetParameter1, targetParameter2);
+ float frequencyHz = extractTargetFrequency(targetParameter1, targetParameter2);
+ addTransitionSegment(duration, amplitude, frequencyHz);
+ return this;
+ }
+
+ /**
+ * Add a duration to sustain the last vibration parameters of this waveform.
+ *
+ * <p>The duration represents how long the vibrator should sustain the last set of
+ * parameters provided to this builder.
+ *
+ * @param duration The length of time the last values should be sustained by the vibrator.
+ * Value must be >= 1ms.
+ * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
+ * chain.
+ * @hide
+ */
+ @TestApi
+ @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
+ @NonNull
+ public WaveformBuilder addSustain(@NonNull Duration duration) {
+ int durationMs = (int) duration.toMillis();
+ Preconditions.checkArgument(durationMs >= 1, "Sustain duration must be >= 1ms");
+ mSegments.add(new StepSegment(mLastAmplitude, mLastFrequencyHz, durationMs));
+ return this;
+ }
+
+ /**
+ * Build the waveform as a single {@link VibrationEffect}.
+ *
+ * <p>The {@link WaveformBuilder} object is still valid after this call, so you can
+ * continue adding more primitives to it and generating more {@link VibrationEffect}s by
+ * calling this method again.
+ *
+ * @return The {@link VibrationEffect} resulting from the list of transitions.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public VibrationEffect build() {
+ if (mSegments.isEmpty()) {
+ throw new IllegalStateException(
+ "WaveformBuilder must have at least one transition to build.");
+ }
+ VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1);
+ effect.validate();
+ return effect;
+ }
+
+ private void checkVibrationParameter(@NonNull VibrationParameter vibrationParameter,
+ String paramName) {
+ Preconditions.checkNotNull(vibrationParameter, "%s is null", paramName);
+ Preconditions.checkArgument(
+ (vibrationParameter instanceof AmplitudeVibrationParameter)
+ || (vibrationParameter instanceof FrequencyVibrationParameter),
+ "%s is a unknown parameter", paramName);
+ }
+
+ private float extractTargetAmplitude(@Nullable VibrationParameter target1,
+ @Nullable VibrationParameter target2) {
+ if (target2 instanceof AmplitudeVibrationParameter) {
+ return ((AmplitudeVibrationParameter) target2).amplitude;
+ }
+ if (target1 instanceof AmplitudeVibrationParameter) {
+ return ((AmplitudeVibrationParameter) target1).amplitude;
+ }
+ return mLastAmplitude;
+ }
+
+ private float extractTargetFrequency(@Nullable VibrationParameter target1,
+ @Nullable VibrationParameter target2) {
+ if (target2 instanceof FrequencyVibrationParameter) {
+ return ((FrequencyVibrationParameter) target2).frequencyHz;
+ }
+ if (target1 instanceof FrequencyVibrationParameter) {
+ return ((FrequencyVibrationParameter) target1).frequencyHz;
+ }
+ return mLastFrequencyHz;
+ }
+
+ private void addTransitionSegment(Duration duration, float targetAmplitude,
+ float targetFrequency) {
+ Preconditions.checkNotNull(duration, "Duration is null");
+ Preconditions.checkArgument(!duration.isNegative(),
+ "Transition duration must be non-negative");
+ int durationMs = (int) duration.toMillis();
+
+ // Ignore transitions with zero duration, but keep values for next additions.
+ if (durationMs > 0) {
+ if ((Math.abs(mLastAmplitude - targetAmplitude) < EPSILON)
+ && (Math.abs(mLastFrequencyHz - targetFrequency) < EPSILON)) {
+ // No value is changing, this can be best represented by a step segment.
+ mSegments.add(new StepSegment(targetAmplitude, targetFrequency, durationMs));
+ } else {
+ mSegments.add(new RampSegment(mLastAmplitude, targetAmplitude,
+ mLastFrequencyHz, targetFrequency, durationMs));
+ }
+ }
+
+ mLastAmplitude = targetAmplitude;
+ mLastFrequencyHz = targetFrequency;
+ }
+ }
+
+ /**
+ * A representation of a single vibration parameter.
+ *
+ * <p>This is to describe a waveform haptic effect, which consists of one or more timed
+ * transitions to a new set of {@link VibrationParameter}s.
+ *
+ * <p>Examples of concrete parameters are the vibration amplitude or frequency.
+ *
+ * @see VibrationEffect.WaveformBuilder
+ * @hide
+ */
+ @TestApi
+ @SuppressWarnings("UserHandleName") // This is not a regular set of parameters, no *Params.
+ public static class VibrationParameter {
+ VibrationParameter() {
+ }
+
+ /**
+ * The target vibration amplitude.
+ *
+ * @param amplitude The amplitude value, between 0 and 1, inclusive, where 0 represents the
+ * vibrator turned off and 1 represents the maximum amplitude the vibrator
+ * can reach across all supported frequencies.
+ * @return The {@link VibrationParameter} instance that represents given amplitude.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static VibrationParameter targetAmplitude(
+ @FloatRange(from = 0, to = 1) float amplitude) {
+ return new AmplitudeVibrationParameter(amplitude);
+ }
+
+ /**
+ * The target vibration frequency.
+ *
+ * @param frequencyHz The frequency value, in hertz.
+ * @return The {@link VibrationParameter} instance that represents given frequency.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static VibrationParameter targetFrequency(@FloatRange(from = 1) float frequencyHz) {
+ return new FrequencyVibrationParameter(frequencyHz);
+ }
+ }
+
+ /** The vibration amplitude, represented by a value in [0,1]. */
+ private static final class AmplitudeVibrationParameter extends VibrationParameter {
+ public final float amplitude;
+
+ AmplitudeVibrationParameter(float amplitude) {
+ Preconditions.checkArgument((amplitude >= 0) && (amplitude <= 1),
+ "Amplitude must be within [0,1]");
+ this.amplitude = amplitude;
+ }
+ }
+
+ /** The vibration frequency, in hertz, or zero to represent undefined frequency. */
+ private static final class FrequencyVibrationParameter extends VibrationParameter {
+ public final float frequencyHz;
+
+ FrequencyVibrationParameter(float frequencyHz) {
+ Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
+ Preconditions.checkArgument(Float.isFinite(frequencyHz), "Frequency must be finite");
+ this.frequencyHz = frequencyHz;
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<VibrationEffect> CREATOR =
+ new Parcelable.Creator<VibrationEffect>() {
+ @Override
+ public VibrationEffect createFromParcel(Parcel in) {
+ return new Composed(in);
+ }
+ @Override
+ public VibrationEffect[] newArray(int size) {
+ return new VibrationEffect[size];
+ }
+ };
+}
diff --git a/android-34/android/os/Vibrator.java b/android-34/android/os/Vibrator.java
new file mode 100644
index 0000000..4e852e3
--- /dev/null
+++ b/android-34/android/os/Vibrator.java
@@ -0,0 +1,751 @@
+/*
+ * 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.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.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.vibrator.IVibrator;
+import android.media.AudioAttributes;
+import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorFrequencyProfile;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * 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
+ */
+ @TestApi
+ public static final int VIBRATION_INTENSITY_OFF = 0;
+
+ /**
+ * Vibration intensity: low.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int VIBRATION_INTENSITY_LOW = 1;
+
+ /**
+ * Vibration intensity: medium.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int VIBRATION_INTENSITY_MEDIUM = 2;
+
+ /**
+ * Vibration intensity: high.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int VIBRATION_INTENSITY_HIGH = 3;
+
+ /**
+ * Vibration effect support: unknown
+ *
+ * <p>The hardware doesn't report its supported effects, so we can't determine whether the
+ * effect is supported or not.
+ */
+ public static final int VIBRATION_EFFECT_SUPPORT_UNKNOWN = 0;
+
+ /**
+ * Vibration effect support: supported
+ *
+ * <p>This effect is supported by the underlying hardware.
+ */
+ public static final int VIBRATION_EFFECT_SUPPORT_YES = 1;
+
+ /**
+ * Vibration effect support: unsupported
+ *
+ * <p>This effect is <b>not</b> natively supported by the underlying hardware, although
+ * the system may still play a fallback vibration.
+ */
+ public static final int VIBRATION_EFFECT_SUPPORT_NO = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"VIBRATION_EFFECT_SUPPORT_"}, value = {
+ VIBRATION_EFFECT_SUPPORT_UNKNOWN,
+ VIBRATION_EFFECT_SUPPORT_YES,
+ VIBRATION_EFFECT_SUPPORT_NO,
+ })
+ public @interface VibrationEffectSupport {}
+
+ /** @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;
+ @Nullable
+ private final Resources mResources;
+
+ // This is lazily loaded only for the few clients that need this (e. Settings app).
+ @Nullable
+ private volatile VibrationConfig mVibrationConfig;
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ @UnsupportedAppUsage
+ public Vibrator() {
+ mPackageName = ActivityThread.currentPackageName();
+ mResources = null;
+ }
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ protected Vibrator(Context context) {
+ mPackageName = context.getOpPackageName();
+ mResources = context.getResources();
+ }
+
+ /**
+ * Get the info describing this vibrator.
+ *
+ * @hide
+ */
+ protected VibratorInfo getInfo() {
+ return VibratorInfo.EMPTY_VIBRATOR_INFO;
+ }
+
+ /** Get the static vibrator configuration from config.xml. */
+ private VibrationConfig getConfig() {
+ if (mVibrationConfig == null) {
+ Resources resources = mResources;
+ if (resources == null) {
+ final Context ctx = ActivityThread.currentActivityThread().getSystemContext();
+ resources = ctx != null ? ctx.getResources() : null;
+ }
+ // This might be constructed more than once, but it only loads static config data from a
+ // xml file, so it would be ok.
+ mVibrationConfig = new VibrationConfig(resources);
+ }
+ return mVibrationConfig;
+ }
+
+ /**
+ * Get the default vibration intensity for given usage.
+ *
+ * @hide
+ */
+ @TestApi
+ @VibrationIntensity
+ public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
+ return getConfig().getDefaultVibrationIntensity(usage);
+ }
+
+ /**
+ * Return the ID of this vibrator.
+ *
+ * @return A non-negative integer representing the id of the vibrator controlled by this
+ * service, or -1 this service is not attached to any physical vibrator.
+ */
+ public int getId() {
+ return getInfo().getId();
+ }
+
+ /**
+ * 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();
+
+ /**
+ * Check whether the vibrator has independent frequency control.
+ *
+ * @return True if the hardware can control the frequency of the vibrations independently of
+ * the vibration amplitude, false otherwise.
+ * @hide
+ */
+ @TestApi
+ public boolean hasFrequencyControl() {
+ // We currently can only control frequency of the vibration using the compose PWLE method.
+ return getInfo().hasCapability(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+
+ /**
+ * Checks whether or not the vibrator supports all components of a given {@link VibrationEffect}
+ * (i.e. the vibrator can play the given effect as intended).
+ *
+ * <p>If this method returns {@code true}, then the VibrationEffect should play as expected.
+ * If {@code false}, playing the VibrationEffect might still make a vibration, but the vibration
+ * may be significantly degraded from the intention.
+ *
+ * <p>This method aggregates the results of feature check methods such as
+ * {@link #hasAmplitudeControl}, {@link #areAllPrimitivesSupported(int...)}, etc, depending
+ * on the features that are actually used by the VibrationEffect.
+ *
+ * @param effect the {@link VibrationEffect} to check if it is supported
+ * @return {@code true} if the vibrator can play the given {@code effect} as intended,
+ * {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
+ return effect.areVibrationFeaturesSupported(this);
+ }
+
+ /**
+ * Check whether the vibrator can be controlled by an external service with the
+ * {@link IExternalVibratorService}.
+ *
+ * @return True if the hardware can be controlled by an external service, otherwise false.
+ * @hide
+ */
+ public boolean hasExternalControl() {
+ return getInfo().hasCapability(IVibrator.CAP_EXTERNAL_CONTROL);
+ }
+
+ /**
+ * Gets the resonant frequency of the vibrator, if applicable.
+ *
+ * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+ * applicable, or if this vibrator is a composite of multiple physical devices with different
+ * frequencies.
+ */
+ public float getResonantFrequency() {
+ return getInfo().getResonantFrequencyHz();
+ }
+
+ /**
+ * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
+ *
+ * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+ * applicable, or if this vibrator is a composite of multiple physical devices with different
+ * Q factors.
+ */
+ public float getQFactor() {
+ return getInfo().getQFactor();
+ }
+
+ /**
+ * Gets the profile that describes the vibrator output across the supported frequency range.
+ *
+ * <p>The profile describes the relative output acceleration that the device can reach when it
+ * vibrates at different frequencies.
+ *
+ * @return The frequency profile for this vibrator, or null if the vibrator does not have
+ * frequency control. If this vibrator is a composite of multiple physical devices then this
+ * will return a profile supported in all devices, or null if the intersection is empty or not
+ * available.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public VibratorFrequencyProfile getFrequencyProfile() {
+ VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile();
+ if (frequencyProfile.isEmpty()) {
+ return null;
+ }
+ return new VibratorFrequencyProfile(frequencyProfile);
+ }
+
+ /**
+ * Return the maximum amplitude the vibrator can play using the audio haptic channels.
+ *
+ * <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a
+ * positive value <code>maxAmplitude</code>, then the signals from the haptic channels of audio
+ * tracks should be in the range <code>[-maxAmplitude, maxAmplitude]</code>.
+ *
+ * @return a positive value representing the maximum absolute value the device can play signals
+ * from audio haptic channels, or {@link Float#NaN NaN} if it's unknown.
+ * @hide
+ */
+ public float getHapticChannelMaximumAmplitude() {
+ return getConfig().getHapticChannelMaximumAmplitude();
+ }
+
+ /**
+ * Configure an always-on haptics effect.
+ *
+ * @param alwaysOnId The board-specific always-on ID to configure.
+ * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls. May only be null when effect is null.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect,
+ @Nullable VibrationAttributes attributes) {
+ return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes);
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable VibrationAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
+ * Vibrate constantly for the specified period of time.
+ *
+ * <p>The app should be in the foreground for the vibration to happen.</p>
+ *
+ * @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.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @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, VibrationAttributes)} 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>
+ *
+ * <p>The app should be in the foreground for the vibration to happen.</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>
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</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, VibrationAttributes)} 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);
+ }
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(VibrationEffect vibe) {
+ vibrate(vibe, new VibrationAttributes.Builder().build());
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ * @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, VibrationAttributes)} instead.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+ vibrate(vibe,
+ attributes == null
+ ? new VibrationAttributes.Builder().build()
+ : new VibrationAttributes.Builder(attributes).build());
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(@NonNull VibrationEffect vibe, @NonNull VibrationAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+ }
+
+ /**
+ * Like {@link #vibrate(VibrationEffect, VibrationAttributes)}, but allows the
+ * caller to specify the vibration is owned by someone else and set a reason for vibration.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
+ String reason, @NonNull VibrationAttributes attributes);
+
+ /**
+ * Query whether the vibrator natively supports the given effects.
+ *
+ * <p>If an effect is not supported, the system may still automatically fall back to playing
+ * a simpler vibration instead, which is not optimised for the specific device. This includes
+ * the unknown case, which can't be determined in advance, that will dynamically attempt to
+ * fall back if the optimised effect fails to play.
+ *
+ * <p>The returned array will be the same length as the query array and the value at a given
+ * index will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index
+ * in the querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't
+ * supported, or {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether
+ * it's supported or not, as some hardware doesn't report its effect capabilities.
+ *
+ * <p>Use {@link #areAllEffectsSupported(int...)} to get a single combined result,
+ * or for convenience when querying exactly one effect.
+ *
+ * @param effectIds Which effects to query for.
+ * @return An array containing the systems current knowledge about whether the given effects
+ * are natively supported by the device, or not.
+ */
+ @NonNull
+ @VibrationEffectSupport
+ public int[] areEffectsSupported(
+ @NonNull @VibrationEffect.EffectType int... effectIds) {
+ VibratorInfo info = getInfo();
+ int[] supported = new int[effectIds.length];
+ for (int i = 0; i < effectIds.length; i++) {
+ supported[i] = info.isEffectSupported(effectIds[i]);
+ }
+ return supported;
+ }
+
+ /**
+ * Query whether the vibrator supports all the given effects. If no argument is provided this
+ * method will always return {@link #VIBRATION_EFFECT_SUPPORT_YES}.
+ *
+ * <p>If an effect is not supported, the system may still automatically fall back to a simpler
+ * vibration instead, which is not optimised for the specific device, however vibration isn't
+ * guaranteed in this case.
+ *
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
+ * supported by the hardware.
+ *
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
+ * query is not supported, and using them may fall back to an un-optimized vibration or no
+ * vibration.
+ *
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know
+ * whether all the effects are supported. It may support any or all of the queried effects,
+ * but there's no way to programmatically know whether a {@link #vibrate} call will successfully
+ * cause a vibration. It's guaranteed, however, that none of the queried effects are
+ * definitively unsupported by the hardware.
+ *
+ * <p>Use {@link #areEffectsSupported(int...)} to get individual results for each effect.
+ *
+ * @param effectIds Which effects to query for.
+ * @return Whether all specified effects are natively supported by the device. Empty query
+ * defaults to {@link #VIBRATION_EFFECT_SUPPORT_YES}.
+ */
+ @VibrationEffectSupport
+ public final int areAllEffectsSupported(
+ @NonNull @VibrationEffect.EffectType int... effectIds) {
+ VibratorInfo info = getInfo();
+ int allSupported = VIBRATION_EFFECT_SUPPORT_YES;
+ for (int effectId : effectIds) {
+ switch (info.isEffectSupported(effectId)) {
+ case VIBRATION_EFFECT_SUPPORT_NO:
+ return VIBRATION_EFFECT_SUPPORT_NO;
+ case VIBRATION_EFFECT_SUPPORT_YES:
+ continue;
+ default: // VIBRATION_EFFECT_SUPPORT_UNKNOWN
+ allSupported = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ break;
+ }
+ }
+ return allSupported;
+ }
+
+ /**
+ * Query whether the vibrator supports the given primitives.
+ *
+ * The returned array will be the same length as the query array and the value at a given index
+ * will contain whether the effect at that same index in the querying array is supported or
+ * not.
+ *
+ * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+ * it is played.
+ *
+ * <p>Use {@link #areAllPrimitivesSupported(int...)} to get a single combined result,
+ * or for convenience when querying exactly one primitive.
+ *
+ * @param primitiveIds Which primitives to query for.
+ * @return Whether the primitives are supported.
+ */
+ @NonNull
+ public boolean[] arePrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
+ VibratorInfo info = getInfo();
+ boolean[] supported = new boolean[primitiveIds.length];
+ for (int i = 0; i < primitiveIds.length; i++) {
+ supported[i] = info.isPrimitiveSupported(primitiveIds[i]);
+ }
+ return supported;
+ }
+
+ /**
+ * Query whether the vibrator supports all of the given primitives. If no argument is provided
+ * this method will always return {@code true}.
+ *
+ * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+ * it is played.
+ *
+ * <p>Use {@link #arePrimitivesSupported(int...)} to get individual results for each primitive.
+ *
+ * @param primitiveIds Which primitives to query for.
+ * @return Whether all specified primitives are supported. Empty query defaults to {@code true}.
+ */
+ public final boolean areAllPrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
+ VibratorInfo info = getInfo();
+ for (int primitiveId : primitiveIds) {
+ if (!info.isPrimitiveSupported(primitiveId)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Query the estimated durations of the given primitives.
+ *
+ * <p>The returned array will be the same length as the query array and the value at a given
+ * index will contain the duration in milliseconds of the effect at the same index in the
+ * querying array.
+ *
+ * <p>The duration will be positive for primitives that are supported and zero for the
+ * unsupported ones, in correspondence with {@link #arePrimitivesSupported(int...)}.
+ *
+ * @param primitiveIds Which primitives to query for.
+ * @return The duration of each primitive, with zeroes for primitives that are not supported.
+ */
+ @NonNull
+ public int[] getPrimitiveDurations(
+ @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
+ VibratorInfo info = getInfo();
+ int[] durations = new int[primitiveIds.length];
+ for (int i = 0; i < primitiveIds.length; i++) {
+ durations[i] = info.getPrimitiveDuration(primitiveIds[i]);
+ }
+ return durations;
+ }
+
+ /**
+ * Turn the vibrator off.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void cancel();
+
+ /**
+ * Cancel specific types of ongoing vibrations.
+ *
+ * @param usageFilter The type of vibration to be cancelled, represented as a bitwise
+ * combination of {@link VibrationAttributes.Usage} values.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void cancel(int usageFilter);
+
+ /**
+ * Check whether the vibrator is vibrating.
+ *
+ * @return True if the hardware is vibrating, otherwise false.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+ public boolean isVibrating() {
+ return false;
+ }
+
+ /**
+ * Listener for when the vibrator state has changed.
+ *
+ * @see #addVibratorStateListener
+ * @see #removeVibratorStateListener
+ * @hide
+ */
+ @SystemApi
+ public interface OnVibratorStateChangedListener {
+ /**
+ * Called when the vibrator state has changed.
+ *
+ * @param isVibrating If true, the vibrator has started vibrating. If false,
+ * it's stopped vibrating.
+ */
+ void onVibratorStateChanged(boolean isVibrating);
+ }
+
+ /**
+ * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
+ * If the listener was previously added and not removed, this call will be ignored.
+ *
+ * @param listener listener to be added
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+ public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ }
+
+ /**
+ * Adds a listener for vibrator state change. If the listener was previously added and not
+ * removed, this call will be ignored.
+ *
+ * @param listener listener to be added
+ * @param executor executor of listener
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ }
+
+ /**
+ * Removes the listener for vibrator state changes. If the listener was not previously
+ * registered, this call will do nothing.
+ *
+ * @param listener listener to be removed
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ }
+}
diff --git a/android-34/android/os/VibratorInfo.java b/android-34/android/os/VibratorInfo.java
new file mode 100644
index 0000000..71ec096
--- /dev/null
+++ b/android-34/android/os/VibratorInfo.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.hardware.vibrator.Braking;
+import android.hardware.vibrator.IVibrator;
+import android.util.MathUtils;
+import android.util.Range;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A VibratorInfo describes the capabilities of a {@link Vibrator}.
+ *
+ * <p>This description includes its capabilities, list of supported effects and composition
+ * primitives.
+ *
+ * @hide
+ */
+public class VibratorInfo implements Parcelable {
+ private static final String TAG = "VibratorInfo";
+
+ /** @hide */
+ public static final VibratorInfo EMPTY_VIBRATOR_INFO = new VibratorInfo.Builder(-1).build();
+
+ private final int mId;
+ private final long mCapabilities;
+ @Nullable
+ private final SparseBooleanArray mSupportedEffects;
+ @Nullable
+ private final SparseBooleanArray mSupportedBraking;
+ private final SparseIntArray mSupportedPrimitives;
+ private final int mPrimitiveDelayMax;
+ private final int mCompositionSizeMax;
+ private final int mPwlePrimitiveDurationMax;
+ private final int mPwleSizeMax;
+ private final float mQFactor;
+ private final FrequencyProfile mFrequencyProfile;
+
+ VibratorInfo(Parcel in) {
+ mId = in.readInt();
+ mCapabilities = in.readLong();
+ mSupportedEffects = in.readSparseBooleanArray();
+ mSupportedBraking = in.readSparseBooleanArray();
+ mSupportedPrimitives = in.readSparseIntArray();
+ mPrimitiveDelayMax = in.readInt();
+ mCompositionSizeMax = in.readInt();
+ mPwlePrimitiveDurationMax = in.readInt();
+ mPwleSizeMax = in.readInt();
+ mQFactor = in.readFloat();
+ mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
+ }
+
+ public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) {
+ this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects,
+ baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives,
+ baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
+ baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
+ baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile);
+ }
+
+ /**
+ * Default constructor.
+ *
+ * @param id The vibrator id.
+ * @param capabilities All capability flags of the vibrator, defined in
+ * IVibrator.CAP_*.
+ * @param supportedEffects All supported predefined effects, enum values from
+ * {@link android.hardware.vibrator.Effect}.
+ * @param supportedBraking All supported braking types, enum values from {@link
+ * Braking}.
+ * @param supportedPrimitives All supported primitive effects, key are enum values from
+ * {@link android.hardware.vibrator.CompositePrimitive} and
+ * values are estimated durations in milliseconds.
+ * @param primitiveDelayMax The maximum delay that can be set to a composition primitive
+ * in milliseconds.
+ * @param compositionSizeMax The maximum number of primitives supported by a composition.
+ * @param pwlePrimitiveDurationMax The maximum duration of a PWLE primitive in milliseconds.
+ * @param pwleSizeMax The maximum number of primitives supported by a PWLE
+ * composition.
+ * @param qFactor The vibrator quality factor.
+ * @param frequencyProfile The description of the vibrator supported frequencies and max
+ * amplitude mappings.
+ * @hide
+ */
+ public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects,
+ @Nullable SparseBooleanArray supportedBraking,
+ @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
+ int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
+ float qFactor, @NonNull FrequencyProfile frequencyProfile) {
+ Preconditions.checkNotNull(supportedPrimitives);
+ Preconditions.checkNotNull(frequencyProfile);
+ mId = id;
+ mCapabilities = capabilities;
+ mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
+ mSupportedBraking = supportedBraking == null ? null : supportedBraking.clone();
+ mSupportedPrimitives = supportedPrimitives.clone();
+ mPrimitiveDelayMax = primitiveDelayMax;
+ mCompositionSizeMax = compositionSizeMax;
+ mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
+ mPwleSizeMax = pwleSizeMax;
+ mQFactor = qFactor;
+ mFrequencyProfile = frequencyProfile;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeLong(mCapabilities);
+ dest.writeSparseBooleanArray(mSupportedEffects);
+ dest.writeSparseBooleanArray(mSupportedBraking);
+ dest.writeSparseIntArray(mSupportedPrimitives);
+ dest.writeInt(mPrimitiveDelayMax);
+ dest.writeInt(mCompositionSizeMax);
+ dest.writeInt(mPwlePrimitiveDurationMax);
+ dest.writeInt(mPwleSizeMax);
+ dest.writeFloat(mQFactor);
+ mFrequencyProfile.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VibratorInfo)) {
+ return false;
+ }
+ VibratorInfo that = (VibratorInfo) o;
+ int supportedPrimitivesCount = mSupportedPrimitives.size();
+ if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
+ return false;
+ }
+ for (int i = 0; i < supportedPrimitivesCount; i++) {
+ if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) {
+ return false;
+ }
+ if (mSupportedPrimitives.valueAt(i) != that.mSupportedPrimitives.valueAt(i)) {
+ return false;
+ }
+ }
+ return mId == that.mId && mCapabilities == that.mCapabilities
+ && mPrimitiveDelayMax == that.mPrimitiveDelayMax
+ && mCompositionSizeMax == that.mCompositionSizeMax
+ && mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
+ && mPwleSizeMax == that.mPwleSizeMax
+ && Objects.equals(mSupportedEffects, that.mSupportedEffects)
+ && Objects.equals(mSupportedBraking, that.mSupportedBraking)
+ && Objects.equals(mQFactor, that.mQFactor)
+ && Objects.equals(mFrequencyProfile, that.mFrequencyProfile);
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
+ mQFactor, mFrequencyProfile);
+ for (int i = 0; i < mSupportedPrimitives.size(); i++) {
+ hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
+ hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ return "VibratorInfo{"
+ + "mId=" + mId
+ + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames())
+ + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
+ + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ + ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames())
+ + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
+ + ", mPrimitiveDelayMax=" + mPrimitiveDelayMax
+ + ", mCompositionSizeMax=" + mCompositionSizeMax
+ + ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
+ + ", mPwleSizeMax=" + mPwleSizeMax
+ + ", mQFactor=" + mQFactor
+ + ", mFrequencyProfile=" + mFrequencyProfile
+ + '}';
+ }
+
+ /** Return the id of this vibrator. */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Check whether the vibrator has amplitude control.
+ *
+ * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+ */
+ public boolean hasAmplitudeControl() {
+ return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
+ }
+
+ /**
+ * Returns a default value to be applied to composed PWLE effects for braking.
+ *
+ * @return a supported braking value, one of android.hardware.vibrator.Braking.*
+ * @hide
+ */
+ public int getDefaultBraking() {
+ if (mSupportedBraking != null) {
+ int size = mSupportedBraking.size();
+ for (int i = 0; i < size; i++) {
+ if (mSupportedBraking.keyAt(i) != Braking.NONE) {
+ return mSupportedBraking.keyAt(i);
+ }
+ }
+ }
+ return Braking.NONE;
+ }
+
+ /** @hide */
+ @Nullable
+ public SparseBooleanArray getSupportedBraking() {
+ if (mSupportedBraking == null) {
+ return null;
+ }
+ return mSupportedBraking.clone();
+ }
+
+ /** @hide */
+ public boolean isBrakingSupportKnown() {
+ return mSupportedBraking != null;
+ }
+
+ /** @hide */
+ public boolean hasBrakingSupport(@Braking int braking) {
+ return (mSupportedBraking != null) && mSupportedBraking.get(braking);
+ }
+
+ /** @hide */
+ public boolean isEffectSupportKnown() {
+ return mSupportedEffects != null;
+ }
+
+ /**
+ * Query whether the vibrator supports the given effect.
+ *
+ * @param effectId Which effects to query for.
+ * @return {@link Vibrator#VIBRATION_EFFECT_SUPPORT_YES} if the effect is supported,
+ * {@link Vibrator#VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or
+ * {@link Vibrator#VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's
+ * supported or not.
+ */
+ @Vibrator.VibrationEffectSupport
+ public int isEffectSupported(@VibrationEffect.EffectType int effectId) {
+ if (mSupportedEffects == null) {
+ return Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ }
+ return mSupportedEffects.get(effectId) ? Vibrator.VIBRATION_EFFECT_SUPPORT_YES
+ : Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
+ }
+
+ /** @hide */
+ @Nullable
+ public SparseBooleanArray getSupportedEffects() {
+ if (mSupportedEffects == null) {
+ return null;
+ }
+ return mSupportedEffects.clone();
+ }
+
+ /**
+ * Query whether the vibrator supports the given primitive.
+ *
+ * @param primitiveId Which primitives to query for.
+ * @return Whether the primitive is supported.
+ */
+ public boolean isPrimitiveSupported(
+ @VibrationEffect.Composition.PrimitiveType int primitiveId) {
+ return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)
+ && (mSupportedPrimitives.indexOfKey(primitiveId) >= 0);
+ }
+
+ /**
+ * Query the estimated duration of given primitive.
+ *
+ * @param primitiveId Which primitives to query for.
+ * @return The duration in milliseconds estimated for the primitive, or zero if primitive not
+ * supported.
+ */
+ public int getPrimitiveDuration(
+ @VibrationEffect.Composition.PrimitiveType int primitiveId) {
+ return mSupportedPrimitives.get(primitiveId);
+ }
+
+ /** @hide */
+ public SparseIntArray getSupportedPrimitives() {
+ return mSupportedPrimitives.clone();
+ }
+
+ /**
+ * Query the maximum delay supported for a primitive in a composed effect.
+ *
+ * @return The max delay in milliseconds, or zero if unlimited.
+ */
+ public int getPrimitiveDelayMax() {
+ return mPrimitiveDelayMax;
+ }
+
+ /**
+ * Query the maximum number of primitives supported in a composed effect.
+ *
+ * @return The max number of primitives supported, or zero if unlimited.
+ */
+ public int getCompositionSizeMax() {
+ return mCompositionSizeMax;
+ }
+
+ /**
+ * Query the maximum duration supported for a primitive in a PWLE composition.
+ *
+ * @return The max duration in milliseconds, or zero if unlimited.
+ */
+ public int getPwlePrimitiveDurationMax() {
+ return mPwlePrimitiveDurationMax;
+ }
+
+ /**
+ * Query the maximum number of primitives supported in a PWLE composition.
+ *
+ * @return The max number of primitives supported, or zero if unlimited.
+ */
+ public int getPwleSizeMax() {
+ return mPwleSizeMax;
+ }
+
+ /**
+ * Check against this vibrator capabilities.
+ *
+ * @param capability one of IVibrator.CAP_*
+ * @return true if this vibrator has this capability, false otherwise
+ * @hide
+ */
+ public boolean hasCapability(long capability) {
+ return (mCapabilities & capability) == capability;
+ }
+
+ /**
+ * Gets the resonant frequency of the vibrator.
+ *
+ * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
+ * this vibrator is a composite of multiple physical devices.
+ */
+ public float getResonantFrequencyHz() {
+ return mFrequencyProfile.mResonantFrequencyHz;
+ }
+
+ /**
+ * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
+ *
+ * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
+ * this vibrator is a composite of multiple physical devices.
+ */
+ public float getQFactor() {
+ return mQFactor;
+ }
+
+ /**
+ * Gets the profile of supported frequencies, including the measurements of maximum relative
+ * output acceleration for supported vibration frequencies.
+ *
+ * <p>If the devices does not have frequency control then the profile should be empty.
+ */
+ @NonNull
+ public FrequencyProfile getFrequencyProfile() {
+ return mFrequencyProfile;
+ }
+
+ protected long getCapabilities() {
+ return mCapabilities;
+ }
+
+ private String[] getCapabilitiesNames() {
+ List<String> names = new ArrayList<>();
+ if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
+ names.add("ON_CALLBACK");
+ }
+ if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) {
+ names.add("PERFORM_CALLBACK");
+ }
+ if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ names.add("COMPOSE_EFFECTS");
+ }
+ if (hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+ names.add("COMPOSE_PWLE_EFFECTS");
+ }
+ if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+ names.add("ALWAYS_ON_CONTROL");
+ }
+ if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
+ names.add("AMPLITUDE_CONTROL");
+ }
+ if (hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)) {
+ names.add("FREQUENCY_CONTROL");
+ }
+ if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+ names.add("EXTERNAL_CONTROL");
+ }
+ if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
+ names.add("EXTERNAL_AMPLITUDE_CONTROL");
+ }
+ return names.toArray(new String[names.size()]);
+ }
+
+ private String[] getSupportedEffectsNames() {
+ if (mSupportedEffects == null) {
+ return new String[0];
+ }
+ String[] names = new String[mSupportedEffects.size()];
+ for (int i = 0; i < mSupportedEffects.size(); i++) {
+ names[i] = VibrationEffect.effectIdToString(mSupportedEffects.keyAt(i));
+ }
+ return names;
+ }
+
+ private String[] getSupportedBrakingNames() {
+ if (mSupportedBraking == null) {
+ return new String[0];
+ }
+ String[] names = new String[mSupportedBraking.size()];
+ for (int i = 0; i < mSupportedBraking.size(); i++) {
+ switch (mSupportedBraking.keyAt(i)) {
+ case Braking.NONE:
+ names[i] = "NONE";
+ break;
+ case Braking.CLAB:
+ names[i] = "CLAB";
+ break;
+ default:
+ names[i] = Integer.toString(mSupportedBraking.keyAt(i));
+ }
+ }
+ return names;
+ }
+
+ private String[] getSupportedPrimitivesNames() {
+ int supportedPrimitivesCount = mSupportedPrimitives.size();
+ String[] names = new String[supportedPrimitivesCount];
+ for (int i = 0; i < supportedPrimitivesCount; i++) {
+ names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i))
+ + "(" + mSupportedPrimitives.valueAt(i) + "ms)";
+ }
+ return names;
+ }
+
+ /**
+ * Describes the maximum relative output acceleration that can be achieved for each supported
+ * frequency in a specific vibrator.
+ *
+ * <p>This profile is defined by the following parameters:
+ *
+ * <ol>
+ * <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz}
+ * provided by the vibrator in hertz.
+ * <li>{@code maxAmplitudes} a list of values in [0,1] provided by the vibrator, where
+ * {@code maxAmplitudes[i]} represents max supported amplitude at frequency
+ * {@code minFrequencyHz + frequencyResolutionHz * i}.
+ * <li>{@code maxFrequencyHz = minFrequencyHz
+ * + frequencyResolutionHz * (maxAmplitudes.length-1)}
+ * </ol>
+ *
+ * @hide
+ */
+ public static final class FrequencyProfile implements Parcelable {
+ @Nullable
+ private final Range<Float> mFrequencyRangeHz;
+ private final float mMinFrequencyHz;
+ private final float mResonantFrequencyHz;
+ private final float mFrequencyResolutionHz;
+ private final float[] mMaxAmplitudes;
+
+ FrequencyProfile(Parcel in) {
+ this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray());
+ }
+
+ /**
+ * Default constructor.
+ *
+ * @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
+ * @param minFrequencyHz Minimum supported frequency, in hertz.
+ * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
+ * amplitude measurements.
+ * @param maxAmplitudes The max amplitude supported by each supported frequency,
+ * starting at minimum frequency with jumps of frequency
+ * resolution.
+ * @hide
+ */
+ public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz,
+ float frequencyResolutionHz, float[] maxAmplitudes) {
+ mMinFrequencyHz = minFrequencyHz;
+ mResonantFrequencyHz = resonantFrequencyHz;
+ mFrequencyResolutionHz = frequencyResolutionHz;
+ mMaxAmplitudes = new float[maxAmplitudes == null ? 0 : maxAmplitudes.length];
+ if (maxAmplitudes != null) {
+ System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
+ }
+
+ // If any required field is undefined or has a bad value then this profile is invalid.
+ boolean isValid = !Float.isNaN(resonantFrequencyHz)
+ && (resonantFrequencyHz > 0)
+ && !Float.isNaN(minFrequencyHz)
+ && (minFrequencyHz > 0)
+ && !Float.isNaN(frequencyResolutionHz)
+ && (frequencyResolutionHz > 0)
+ && (mMaxAmplitudes.length > 0);
+
+ // If any max amplitude is outside the allowed range then this profile is invalid.
+ for (int i = 0; i < mMaxAmplitudes.length; i++) {
+ isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1);
+ }
+
+ float maxFrequencyHz = isValid
+ ? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1)
+ : Float.NaN;
+
+ // If the constraint min < resonant < max is not met then it is invalid.
+ isValid &= !Float.isNaN(maxFrequencyHz)
+ && (resonantFrequencyHz >= minFrequencyHz)
+ && (resonantFrequencyHz <= maxFrequencyHz)
+ && (minFrequencyHz < maxFrequencyHz);
+
+ mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null;
+ }
+
+ /** Returns true if the supported frequency range is empty. */
+ public boolean isEmpty() {
+ return mFrequencyRangeHz == null;
+ }
+
+ /** Returns the supported frequency range, in hertz. */
+ @Nullable
+ public Range<Float> getFrequencyRangeHz() {
+ return mFrequencyRangeHz;
+ }
+
+ /**
+ * Returns the maximum relative amplitude the vibrator can reach while playing at the
+ * given frequency.
+ *
+ * @param frequencyHz frequency, in hertz, for query.
+ * @return A value in [0,1] representing the max relative amplitude supported at the given
+ * frequency. This will return 0 if the frequency is outside the supported range, or if the
+ * supported frequency range is empty.
+ */
+ public float getMaxAmplitude(float frequencyHz) {
+ if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) {
+ // Unsupported frequency requested, vibrator cannot play at this frequency.
+ return 0;
+ }
+
+ // Subtract minFrequencyHz to simplify offset calculations.
+ float mappingFreq = frequencyHz - mMinFrequencyHz;
+
+ // Find the bucket to interpolate within.
+ // Any calculated index should be safe, except exactly equal to max amplitude can be
+ // one step too high, so constrain it to guarantee safety.
+ int startIdx = MathUtils.constrain(
+ /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz),
+ /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+ int nextIdx = MathUtils.constrain(
+ /* amount= */ startIdx + 1,
+ /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+
+ // Linearly interpolate the amplitudes based on the frequency range of the bucket.
+ return MathUtils.constrainedMap(
+ mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx],
+ startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz,
+ mappingFreq);
+ }
+
+ /** Returns the raw list of maximum relative output accelerations from the vibrator. */
+ @NonNull
+ public float[] getMaxAmplitudes() {
+ return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length);
+ }
+
+ /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */
+ public float getFrequencyResolutionHz() {
+ return mFrequencyResolutionHz;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mResonantFrequencyHz);
+ dest.writeFloat(mMinFrequencyHz);
+ dest.writeFloat(mFrequencyResolutionHz);
+ dest.writeFloatArray(mMaxAmplitudes);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FrequencyProfile)) {
+ return false;
+ }
+ FrequencyProfile that = (FrequencyProfile) o;
+ return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
+ && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
+ && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
+ && Arrays.equals(mMaxAmplitudes, that.mMaxAmplitudes);
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz,
+ mFrequencyResolutionHz);
+ hashCode = 31 * hashCode + Arrays.hashCode(mMaxAmplitudes);
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ return "FrequencyProfile{"
+ + "mFrequencyRange=" + mFrequencyRangeHz
+ + ", mMinFrequency=" + mMinFrequencyHz
+ + ", mResonantFrequency=" + mResonantFrequencyHz
+ + ", mFrequencyResolution=" + mFrequencyResolutionHz
+ + ", mMaxAmplitudes count=" + mMaxAmplitudes.length
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<FrequencyProfile> CREATOR =
+ new Creator<FrequencyProfile>() {
+ @Override
+ public FrequencyProfile createFromParcel(Parcel in) {
+ return new FrequencyProfile(in);
+ }
+
+ @Override
+ public FrequencyProfile[] newArray(int size) {
+ return new FrequencyProfile[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static final class Builder {
+ private final int mId;
+ private long mCapabilities;
+ private SparseBooleanArray mSupportedEffects;
+ private SparseBooleanArray mSupportedBraking;
+ private SparseIntArray mSupportedPrimitives = new SparseIntArray();
+ private int mPrimitiveDelayMax;
+ private int mCompositionSizeMax;
+ private int mPwlePrimitiveDurationMax;
+ private int mPwleSizeMax;
+ private float mQFactor = Float.NaN;
+ private FrequencyProfile mFrequencyProfile =
+ new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
+
+ /** A builder class for a {@link VibratorInfo}. */
+ public Builder(int id) {
+ mId = id;
+ }
+
+ /** Configure the vibrator capabilities with a combination of IVibrator.CAP_* values. */
+ @NonNull
+ public Builder setCapabilities(long capabilities) {
+ mCapabilities = capabilities;
+ return this;
+ }
+
+ /** Configure the effects supported with {@link android.hardware.vibrator.Effect} values. */
+ @NonNull
+ public Builder setSupportedEffects(int... supportedEffects) {
+ mSupportedEffects = toSparseBooleanArray(supportedEffects);
+ return this;
+ }
+
+ /** Configure braking supported with {@link android.hardware.vibrator.Braking} values. */
+ @NonNull
+ public Builder setSupportedBraking(int... supportedBraking) {
+ mSupportedBraking = toSparseBooleanArray(supportedBraking);
+ return this;
+ }
+
+ /** Configure maximum duration, in milliseconds, of a PWLE primitive. */
+ @NonNull
+ public Builder setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax) {
+ mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
+ return this;
+ }
+
+ /** Configure maximum number of primitives supported in a single PWLE composed effect. */
+ @NonNull
+ public Builder setPwleSizeMax(int pwleSizeMax) {
+ mPwleSizeMax = pwleSizeMax;
+ return this;
+ }
+
+ /** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */
+ @NonNull
+ public Builder setSupportedPrimitive(int primitiveId, int duration) {
+ mSupportedPrimitives.put(primitiveId, duration);
+ return this;
+ }
+
+ /** Configure maximum delay, in milliseconds, supported in a composed effect primitive. */
+ @NonNull
+ public Builder setPrimitiveDelayMax(int primitiveDelayMax) {
+ mPrimitiveDelayMax = primitiveDelayMax;
+ return this;
+ }
+
+ /** Configure maximum number of primitives supported in a single composed effect. */
+ @NonNull
+ public Builder setCompositionSizeMax(int compositionSizeMax) {
+ mCompositionSizeMax = compositionSizeMax;
+ return this;
+ }
+
+ /** Configure the vibrator quality factor. */
+ @NonNull
+ public Builder setQFactor(float qFactor) {
+ mQFactor = qFactor;
+ return this;
+ }
+
+ /** Configure the vibrator frequency information like resonant frequency and bandwidth. */
+ @NonNull
+ public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
+ mFrequencyProfile = frequencyProfile;
+ return this;
+ }
+
+ /** Build the configured {@link VibratorInfo}. */
+ @NonNull
+ public VibratorInfo build() {
+ return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
+ mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
+ mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile);
+ }
+
+ /**
+ * Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is
+ * mapped
+ * to {@code true}.
+ */
+ @Nullable
+ private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) {
+ if (supportedKeys == null) {
+ return null;
+ }
+ SparseBooleanArray array = new SparseBooleanArray();
+ for (int key : supportedKeys) {
+ array.put(key, true);
+ }
+ return array;
+ }
+ }
+
+ @NonNull
+ public static final Creator<VibratorInfo> CREATOR =
+ new Creator<VibratorInfo>() {
+ @Override
+ public VibratorInfo createFromParcel(Parcel in) {
+ return new VibratorInfo(in);
+ }
+
+ @Override
+ public VibratorInfo[] newArray(int size) {
+ return new VibratorInfo[size];
+ }
+ };
+}
diff --git a/android-34/android/os/VibratorManager.java b/android-34/android/os/VibratorManager.java
new file mode 100644
index 0000000..f506ef8
--- /dev/null
+++ b/android-34/android/os/VibratorManager.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * Provides access to all vibrators from the device, as well as the ability to run them
+ * in a synchronized fashion.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
+ */
+@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
+public abstract class VibratorManager {
+ private static final String TAG = "VibratorManager";
+
+ private final String mPackageName;
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ public VibratorManager() {
+ mPackageName = ActivityThread.currentPackageName();
+ }
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ protected VibratorManager(Context context) {
+ mPackageName = context.getOpPackageName();
+ }
+
+ /**
+ * List all available vibrator ids, returning a possible empty list.
+ *
+ * @return An array containing the ids of the vibrators available on the device.
+ */
+ @NonNull
+ public abstract int[] getVibratorIds();
+
+ /**
+ * Retrieve a single vibrator by id.
+ *
+ * @param vibratorId The id of the vibrator to be retrieved.
+ * @return The vibrator with given {@code vibratorId}, never null.
+ */
+ @NonNull
+ public abstract Vibrator getVibrator(int vibratorId);
+
+ /**
+ * Returns the default Vibrator for the device.
+ */
+ @NonNull
+ public abstract Vibrator getDefaultVibrator();
+
+ /**
+ * Configure an always-on haptics effect.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable CombinedVibration effect, @Nullable VibrationAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
+ * Vibrate with a given combination of effects.
+ *
+ * <p>
+ * Pass in a {@link CombinedVibration} representing a combination of {@link
+ * VibrationEffect VibrationEffects} to be played on one or more vibrators.
+ * </p>
+ *
+ * <p>The app should be in foreground for the vibration to happen.</p>
+ *
+ * @param effect a combination of effects to be performed by one or more vibrators.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public final void vibrate(@NonNull CombinedVibration effect) {
+ vibrate(effect, null);
+ }
+
+ /**
+ * Vibrate with a given combination of effects.
+ *
+ * <p>
+ * Pass in a {@link CombinedVibration} representing a combination of {@link
+ * VibrationEffect} to be played on one or more vibrators.
+ * </p>
+ *
+ * <p>The app should be in foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param effect a combination of effects to be performed by one or more vibrators.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public final void vibrate(@NonNull CombinedVibration effect,
+ @Nullable VibrationAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, effect, null, attributes);
+ }
+
+ /**
+ * Like {@link #vibrate(CombinedVibration, VibrationAttributes)}, 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, @NonNull CombinedVibration effect,
+ String reason, @Nullable VibrationAttributes attributes);
+
+ /**
+ * Turn all the vibrators off.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void cancel();
+
+ /**
+ * Cancel specific types of ongoing vibrations.
+ *
+ * @param usageFilter The type of vibration to be cancelled, represented as a bitwise
+ * combination of {@link VibrationAttributes.Usage} values.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void cancel(int usageFilter);
+}
diff --git a/android-34/android/os/VibratorPerfTest.java b/android-34/android/os/VibratorPerfTest.java
new file mode 100644
index 0000000..0efe8cf
--- /dev/null
+++ b/android-34/android/os/VibratorPerfTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.concurrent.TimeUnit.SECONDS;
+
+import android.content.Context;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class VibratorPerfTest {
+ @Rule
+ public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+ private Vibrator mVibrator;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mVibrator = context.getSystemService(Vibrator.class);
+ }
+
+ @Test
+ public void testEffectClick() {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ while (state.keepRunning()) {
+ mVibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ }
+ }
+
+ @Test
+ public void testOneShot() {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ while (state.keepRunning()) {
+ mVibrator.vibrate(VibrationEffect.createOneShot(SECONDS.toMillis(2),
+ VibrationEffect.DEFAULT_AMPLITUDE));
+ }
+ }
+
+ @Test
+ public void testWaveform() {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ long[] timings = new long[]{SECONDS.toMillis(1), SECONDS.toMillis(2), SECONDS.toMillis(1)};
+ while (state.keepRunning()) {
+ mVibrator.vibrate(VibrationEffect.createWaveform(timings, -1));
+ }
+ }
+
+ @Test
+ public void testCompose() {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ while (state.keepRunning()) {
+ mVibrator.vibrate(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 100)
+ .compose());
+ }
+ }
+
+ @Test
+ public void testAreEffectsSupported() {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ int[] effects = new int[]{VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK};
+ while (state.keepRunning()) {
+ mVibrator.areEffectsSupported(effects);
+ }
+ }
+
+ @Test
+ public void testArePrimitivesSupported() {
+ final BenchmarkState state = mBenchmarkRule.getState();
+ int[] primitives = new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK};
+ while (state.keepRunning()) {
+ mVibrator.arePrimitivesSupported(primitives);
+ }
+ }
+}
diff --git a/android-34/android/os/VintfObject.java b/android-34/android/os/VintfObject.java
new file mode 100644
index 0000000..1f11197
--- /dev/null
+++ b/android-34/android/os/VintfObject.java
@@ -0,0 +1,150 @@
+/*
+ * 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.TestApi;
+import android.util.Slog;
+
+import java.util.Map;
+
+/**
+ * Java API for libvintf.
+ *
+ * @hide
+ */
+@TestApi
+public class VintfObject {
+
+ private static final String LOG_TAG = "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.)
+ *
+ * @deprecated Checking compatibility against an OTA package is no longer
+ * supported because the format of VINTF metadata in the OTA package may not
+ * be recognized by the current system.
+ *
+ * <p>
+ * <ul>
+ * <li>This function always returns 0 for non-empty {@code packageInfo}.
+ * </li>
+ * <li>This function returns the result of {@link #verifyWithoutAvb} for
+ * null or empty {@code packageInfo}.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @Deprecated
+ public static int verify(String[] packageInfo) {
+ if (packageInfo != null && packageInfo.length > 0) {
+ Slog.w(LOG_TAG, "VintfObject.verify() with non-empty packageInfo is deprecated. "
+ + "Skipping compatibility checks for update package.");
+ return 0;
+ }
+ Slog.w(LOG_TAG, "VintfObject.verify() is deprecated. Call verifyWithoutAvb() instead.");
+ return verifyWithoutAvb();
+ }
+
+ /**
+ * 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.
+ *
+ * For AIDL HALs, the version is a single number
+ * (e.g. "android.hardware.light@1"). Historically, this API strips the
+ * version number for AIDL HALs (e.g. "android.hardware.light"). Users
+ * of this API must be able to handle both for backwards compatibility.
+ *
+ * @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 the PLATFORM_SEPOLICY_VERSION build flag available in framework
+ * compatibility matrix.
+ *
+ * @hide
+ */
+ @TestApi
+ public static native @NonNull String getPlatformSepolicyVersion();
+
+ /**
+ * @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-34/android/os/VintfRuntimeInfo.java b/android-34/android/os/VintfRuntimeInfo.java
new file mode 100644
index 0000000..f17039b
--- /dev/null
+++ b/android-34/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-34/android/os/WakeLockStats.java b/android-34/android/os/WakeLockStats.java
new file mode 100644
index 0000000..05a7313
--- /dev/null
+++ b/android-34/android/os/WakeLockStats.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Snapshot of wake lock stats.
+ * @hide
+ */
+public final class WakeLockStats implements Parcelable {
+
+ /** @hide */
+ public static class WakeLock {
+ public final int uid;
+ @NonNull
+ public final String name;
+ public final int timesAcquired;
+ public final long totalTimeHeldMs;
+
+ /**
+ * Time in milliseconds that the lock has been held or 0 if not currently holding the lock
+ */
+ public final long timeHeldMs;
+
+ public WakeLock(int uid, @NonNull String name, int timesAcquired, long totalTimeHeldMs,
+ long timeHeldMs) {
+ this.uid = uid;
+ this.name = name;
+ this.timesAcquired = timesAcquired;
+ this.totalTimeHeldMs = totalTimeHeldMs;
+ this.timeHeldMs = timeHeldMs;
+ }
+
+ private WakeLock(Parcel in) {
+ uid = in.readInt();
+ name = in.readString();
+ timesAcquired = in.readInt();
+ totalTimeHeldMs = in.readLong();
+ timeHeldMs = in.readLong();
+ }
+
+ private void writeToParcel(Parcel out) {
+ out.writeInt(uid);
+ out.writeString(name);
+ out.writeInt(timesAcquired);
+ out.writeLong(totalTimeHeldMs);
+ out.writeLong(timeHeldMs);
+ }
+
+ @Override
+ public String toString() {
+ return "WakeLock{"
+ + "uid=" + uid
+ + ", name='" + name + '\''
+ + ", timesAcquired=" + timesAcquired
+ + ", totalTimeHeldMs=" + totalTimeHeldMs
+ + ", timeHeldMs=" + timeHeldMs
+ + '}';
+ }
+ }
+
+ private final List<WakeLock> mWakeLocks;
+
+ /** @hide **/
+ public WakeLockStats(@NonNull List<WakeLock> wakeLocks) {
+ mWakeLocks = wakeLocks;
+ }
+
+ @NonNull
+ public List<WakeLock> getWakeLocks() {
+ return mWakeLocks;
+ }
+
+ private WakeLockStats(Parcel in) {
+ final int size = in.readInt();
+ mWakeLocks = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mWakeLocks.add(new WakeLock(in));
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ final int size = mWakeLocks.size();
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ WakeLock stats = mWakeLocks.get(i);
+ stats.writeToParcel(out);
+ }
+ }
+
+ @NonNull
+ public static final Creator<WakeLockStats> CREATOR =
+ new Creator<WakeLockStats>() {
+ public WakeLockStats createFromParcel(Parcel in) {
+ return new WakeLockStats(in);
+ }
+
+ public WakeLockStats[] newArray(int size) {
+ return new WakeLockStats[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "WakeLockStats " + mWakeLocks;
+ }
+}
diff --git a/android-34/android/os/WorkSource.java b/android-34/android/os/WorkSource.java
new file mode 100644
index 0000000..bc80c8b
--- /dev/null
+++ b/android-34/android/os/WorkSource.java
@@ -0,0 +1,1247 @@
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+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;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Describes the source of some work that may be done by someone else.
+ * Currently the public representation of what a work source 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
+ @NonNull
+ int[] mUids = new int[0];
+
+ @UnsupportedAppUsage
+ @Nullable
+ 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.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ static final WorkSource sTmpWorkSource = new WorkSource(0);
+ /**
+ * For returning newbie work from a modification operation.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ static WorkSource sNewbWork;
+ /**
+ * For returning gone work from a modification operation.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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;
+ mUids = orig.mUids.clone();
+ mNames = orig.mNames != null ? orig.mNames.clone() : 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;
+ }
+ }
+
+
+ /**
+ * Creates a work source with the given uid.
+ * @param uid the uid performing the work
+ * @hide
+ */
+ @SystemApi
+ public WorkSource(int uid) {
+ mNum = 1;
+ mUids = new int[] { uid, 0 };
+ mNames = null;
+ mChains = null;
+ }
+
+ /**
+ * Creates a work source with the given uid and package name.
+ * @param uid the uid performing the work
+ * @param packageName the package performing the work
+ * @hide
+ */
+ @SystemApi
+ public WorkSource(int uid, @NonNull String packageName) {
+ Objects.requireNonNull(packageName, "packageName can't be null");
+ mNum = 1;
+ mUids = new int[] { uid, 0 };
+ mNames = new String[] { packageName, null };
+ mChains = null;
+ }
+
+ @UnsupportedAppUsage
+ WorkSource(Parcel in) {
+ mNum = in.readInt();
+ mUids = Objects.requireNonNullElse(in.createIntArray(), new int[0]);
+ mNames = in.createStringArray();
+
+ int numChains = in.readInt();
+ if (numChains >= 0) {
+ mChains = new ArrayList<>(numChains);
+ in.readParcelableList(mChains, WorkChain.class.getClassLoader(), android.os.WorkSource.WorkChain.class);
+ } 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;
+ }
+
+ /**
+ * Returns the number of uids in this work source.
+ * @hide
+ */
+ @SystemApi
+ public int size() {
+ return mNum;
+ }
+
+ /**
+ * @deprecated use {{@link #getUid(int)}} instead.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ public int get(int index) {
+ return getUid(index);
+ }
+
+ /**
+ * Get the uid at the given index.
+ * If {@code index} < 0 or {@code index} >= {@link #size() N}, then the behavior is undefined.
+ * @hide
+ */
+ @SystemApi
+ public int getUid(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();
+ }
+
+ /**
+ * @deprecated use {{@link #getPackageName(int)}} instead.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ public String getName(int index) {
+ return getPackageName(index);
+ }
+
+ /**
+ * Get the package name at the given index.
+ * If {@code index} < 0 or {@code index} >= {@link #size() N}, then the behavior is undefined.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getPackageName(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.
+ */
+ private 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(@Nullable 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) {
+ clear();
+ return;
+ }
+ mNum = other.mNum;
+ if (mUids.length >= mNum) { // this has more data than other
+ 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;
+ }
+
+ 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.length == 0) 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.length == 0) {
+ 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));
+ chainAdded = true;
+ }
+ }
+ }
+
+ return uidAdded || chainAdded;
+ }
+ }
+
+ /**
+ * Returns a copy of this work source without any package names.
+ * If any {@link WorkChain WorkChains} are present, they are left intact.
+ *
+ * @return a {@link WorkSource} without any package names.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public WorkSource withoutNames() {
+ final WorkSource copy = new WorkSource(this);
+ copy.clearNames();
+ return copy;
+ }
+
+ /**
+ * 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 */
+ @UnsupportedAppUsage
+ @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 */
+ @UnsupportedAppUsage
+ @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.
+ */
+ @SystemApi
+ public boolean isEmpty() {
+ return mNum == 0 && (mChains == null || mChains.isEmpty());
+ }
+
+ /**
+ * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public List<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;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ 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.length == 0) {
+ 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.length == 0) {
+ 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 (mNum == 0) {
+ 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;
+ }
+
+ @NonNull
+ @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(@Nullable 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 naive 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 dumpDebug(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);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<WorkSource> CREATOR = new Parcelable.Creator<>() {
+ public WorkSource createFromParcel(Parcel in) {
+ return new WorkSource(in);
+ }
+ public WorkSource[] newArray(int size) {
+ return new WorkSource[size];
+ }
+ };
+}
diff --git a/android-34/android/os/ZygoteProcess.java b/android-34/android/os/ZygoteProcess.java
new file mode 100644
index 0000000..3cb5c60
--- /dev/null
+++ b/android-34/android/os/ZygoteProcess.java
@@ -0,0 +1,1317 @@
+/*
+ * 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 android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ApplicationInfo;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+import android.util.Pair;
+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.Map;
+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 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);
+
+ // This constructor is used to create the primary and secondary Zygotes, which can support
+ // Unspecialized App Process Pools.
+ mUsapPoolSupported = true;
+ }
+
+ public ZygoteProcess(LocalSocketAddress primarySocketAddress,
+ LocalSocketAddress secondarySocketAddress) {
+ mZygoteSocketAddress = primarySocketAddress;
+ mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+ mUsapPoolSocketAddress = null;
+ mUsapPoolSecondarySocketAddress = null;
+
+ // This constructor is used to create the primary and secondary Zygotes, which CAN NOT
+ // support Unspecialized App Process Pools.
+ mUsapPoolSupported = false;
+ }
+
+ 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 deny list. 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> mApiDenylistExemptions = 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 this Zygote supports the creation and maintenance of a USAP pool.
+ *
+ * Currently only the primary and secondary Zygotes support USAP pools. Any
+ * child Zygotes will be unable to create or use a USAP pool.
+ */
+ private final boolean mUsapPoolSupported;
+
+ /**
+ * 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 zygotePolicyFlags Flags used to determine how to launch the application.
+ * @param isTopApp Whether the process starts for high priority application.
+ * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+ * started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
+ * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
+ * volume UUID and inode number.
+ * @param bindMountAppsData whether zygote needs to mount CE and DE data.
+ * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
+ *
+ * @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,
+ int zygotePolicyFlags,
+ boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ allowlistedDataInfoList,
+ boolean bindMountAppsData,
+ boolean bindMountAppStorageDirs,
+ @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, zygotePolicyFlags, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData,
+ bindMountAppStorageDirs, 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, int zygotePolicyFlags, @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 an 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 (shouldAttemptUsapLaunch(zygotePolicyFlags, 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");
+ }
+ }
+ }
+
+ /**
+ * Test various member properties and parameters to determine if a launch event should be
+ * handled using an Unspecialized App Process Pool or not.
+ *
+ * @param zygotePolicyFlags Policy flags indicating special behavioral observations about the
+ * Zygote command
+ * @param args Arguments that will be passed to the Zygote
+ * @return If the command should be sent to a USAP Pool member or an actual Zygote
+ */
+ private boolean shouldAttemptUsapLaunch(int zygotePolicyFlags, ArrayList<String> args) {
+ return mUsapPoolSupported
+ && mUsapPoolEnabled
+ && policySpecifiesUsapPoolLaunch(zygotePolicyFlags)
+ && commandSupportedByUsap(args);
+ }
+
+ /**
+ * Tests a Zygote policy flag set for various properties that determine if it is eligible for
+ * being handled by an Unspecialized App Process Pool.
+ *
+ * @param zygotePolicyFlags Policy flags indicating special behavioral observations about the
+ * Zygote command
+ * @return If the policy allows for use of a USAP pool
+ */
+ private static boolean policySpecifiesUsapPoolLaunch(int zygotePolicyFlags) {
+ /*
+ * Zygote USAP Pool Policy: Launch the new process from the USAP Pool iff the launch event
+ * is latency sensitive but *NOT* a system process. All system processes are equally
+ * important so we don't want to prioritize one over another.
+ */
+ return (zygotePolicyFlags
+ & (ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS | ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE))
+ == ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+ }
+
+ /**
+ * Flags that may not be passed to a USAP. These may appear as prefixes to individual Zygote
+ * arguments.
+ */
+ private static final String[] INVALID_USAP_FLAGS = {
+ "--query-abi-list",
+ "--get-pid",
+ "--preload-default",
+ "--preload-package",
+ "--preload-app",
+ "--start-child-zygote",
+ "--set-api-denylist-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 commandSupportedByUsap(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.
+ if (Zygote.getWrapProperty(flag.substring(12)) != null) {
+ 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 zygotePolicyFlags Flags used to determine how to launch the application.
+ * @param isTopApp Whether the process starts for high priority application.
+ * @param disabledCompatChanges a list of disabled compat changes for the process being started.
+ * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
+ * and inode number.
+ * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
+ * volume UUID and inode number.
+ * @param bindMountAppsData whether zygote needs to mount CE and DE data.
+ * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
+ * @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,
+ int zygotePolicyFlags,
+ boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
+ @Nullable Map<String, Pair<String, Long>>
+ allowlistedDataInfoList,
+ boolean bindMountAppsData,
+ boolean bindMountAppStorageDirs,
+ @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_INSTALLER) {
+ argsForZygote.add("--mount-external-installer");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
+ argsForZygote.add("--mount-external-pass-through");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+ argsForZygote.add("--mount-external-android-writable");
+ }
+
+ argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
+
+ // --setgroups is a comma-separated list
+ if (gids != null && gids.length > 0) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("--setgroups=");
+
+ final 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);
+ }
+
+ if (isTopApp) {
+ argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
+ }
+ if (pkgDataInfoMap != null && pkgDataInfoMap.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.PKG_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : pkgDataInfoMap.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
+ if (allowlistedDataInfoList != null && allowlistedDataInfoList.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.ALLOWLISTED_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : allowlistedDataInfoList.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
+
+ if (bindMountAppStorageDirs) {
+ argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS);
+ }
+
+ if (bindMountAppsData) {
+ argsForZygote.add(Zygote.BIND_MOUNT_APP_DATA_DIRS);
+ }
+
+ if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("--disabled-compat-changes=");
+
+ int sz = disabledCompatChanges.length;
+ for (int i = 0; i < sz; i++) {
+ if (i != 0) {
+ sb.append(',');
+ }
+ sb.append(disabledCompatChanges[i]);
+ }
+
+ argsForZygote.add(sb.toString());
+ }
+
+ 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),
+ zygotePolicyFlags,
+ argsForZygote);
+ }
+ }
+
+ private boolean fetchUsapPoolEnabledProp() {
+ boolean origVal = mUsapPoolEnabled;
+
+ mUsapPoolEnabled = ZygoteConfig.getBool(
+ ZygoteConfig.USAP_POOL_ENABLED, ZygoteConfig.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() {
+ // If this Zygote doesn't support USAPs there is no need to fetch any
+ // properties.
+ if (!mUsapPoolSupported) return false;
+
+ final long currentTimestamp = SystemClock.elapsedRealtime();
+
+ 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);
+ }
+ }
+
+ /**
+ * Notify the Zygote processes that boot completed.
+ */
+ public void bootCompleted() {
+ // Notify both the 32-bit and 64-bit zygote.
+ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+ bootCompleted(Build.SUPPORTED_32_BIT_ABIS[0]);
+ }
+ if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+ bootCompleted(Build.SUPPORTED_64_BIT_ABIS[0]);
+ }
+ }
+
+ private void bootCompleted(String abi) {
+ try {
+ synchronized (mLock) {
+ ZygoteState state = openZygoteSocketIfNeeded(abi);
+ state.mZygoteOutputWriter.write("1\n--boot-completed\n");
+ state.mZygoteOutputWriter.flush();
+ state.mZygoteInputStream.readInt();
+ }
+ } catch (Exception ex) {
+ throw new RuntimeException("Failed to inform zygote of boot_completed", ex);
+ }
+ }
+
+ /**
+ * Push hidden API deny-listing 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
+ * allowed/public APIs (i.e. allowed, no logging of usage).
+ */
+ public boolean setApiDenylistExemptions(List<String> exemptions) {
+ synchronized (mLock) {
+ mApiDenylistExemptions = exemptions;
+ boolean ok = maybeSetApiDenylistExemptions(primaryZygoteState, true);
+ if (ok) {
+ ok = maybeSetApiDenylistExemptions(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 maybeSetApiDenylistExemptions(ZygoteState state, boolean sendIfEmpty) {
+ if (state == null || state.isClosed()) {
+ Slog.e(LOG_TAG, "Can't set API denylist exemptions: no zygote connection");
+ return false;
+ } else if (!sendIfEmpty && mApiDenylistExemptions.isEmpty()) {
+ return true;
+ }
+
+ try {
+ state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1));
+ state.mZygoteOutputWriter.newLine();
+ state.mZygoteOutputWriter.write("--set-api-denylist-exemptions");
+ state.mZygoteOutputWriter.newLine();
+ for (int i = 0; i < mApiDenylistExemptions.size(); ++i) {
+ state.mZygoteOutputWriter.write(mApiDenylistExemptions.get(i));
+ state.mZygoteOutputWriter.newLine();
+ }
+ state.mZygoteOutputWriter.flush();
+ int status = state.mZygoteInputStream.readInt();
+ if (status != 0) {
+ Slog.e(LOG_TAG, "Failed to set API denylist exemptions; status " + status);
+ }
+ return true;
+ } catch (IOException ioe) {
+ Slog.e(LOG_TAG, "Failed to set API denylist exemptions", ioe);
+ mApiDenylistExemptions = 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);
+
+ maybeSetApiDenylistExemptions(primaryZygoteState, false);
+ maybeSetHiddenApiAccessLogSampleRate(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);
+
+ maybeSetApiDenylistExemptions(secondaryZygoteState, false);
+ maybeSetHiddenApiAccessLogSampleRate(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 {
+ // We will bind mount app data dirs so app zygote can't access /data/data, while
+ // we don't need to bind mount storage dirs as /storage won't be mounted.
+ result = startViaZygote(processClass, niceName, uid, gid,
+ gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
+ abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
+ true /* startChildZygote */, null /* packageName */,
+ ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
+ null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
+ null /* allowlistedDataInfoList */, true /* bindMountAppsData*/,
+ /* bindMountAppStorageDirs */ false, extraArgs);
+
+ } catch (ZygoteStartFailedEx ex) {
+ throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
+ }
+
+ return new ChildZygoteProcess(serverAddress, result.pid);
+ }
+}
diff --git a/android-34/android/os/connectivity/CellularBatteryStats.java b/android-34/android/os/connectivity/CellularBatteryStats.java
new file mode 100644
index 0000000..fc17002
--- /dev/null
+++ b/android-34/android/os/connectivity/CellularBatteryStats.java
@@ -0,0 +1,345 @@
+/*
+ * 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.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.CellSignalStrength;
+import android.telephony.ModemActivityInfo;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * API for Cellular power stats
+ *
+ * @hide
+ */
+@SystemApi
+public final class CellularBatteryStats implements Parcelable {
+
+ private final long mLoggingDurationMs;
+ private final long mKernelActiveTimeMs;
+ private final long mNumPacketsTx;
+ private final long mNumBytesTx;
+ private final long mNumPacketsRx;
+ private final long mNumBytesRx;
+ private final long mSleepTimeMs;
+ private final long mIdleTimeMs;
+ private final long mRxTimeMs;
+ private final long mEnergyConsumedMaMs;
+ private final long[] mTimeInRatMs;
+ private final long[] mTimeInRxSignalStrengthLevelMs;
+ private final long[] mTxTimeMs;
+ private final long mMonitoredRailChargeConsumedMaMs;
+
+ public static final @NonNull Parcelable.Creator<CellularBatteryStats> CREATOR =
+ new Parcelable.Creator<CellularBatteryStats>() {
+ public CellularBatteryStats createFromParcel(Parcel in) {
+ long loggingDurationMs = in.readLong();
+ long kernelActiveTimeMs = in.readLong();
+ long numPacketsTx = in.readLong();
+ long numBytesTx = in.readLong();
+ long numPacketsRx = in.readLong();
+ long numBytesRx = in.readLong();
+ long sleepTimeMs = in.readLong();
+ long idleTimeMs = in.readLong();
+ long rxTimeMs = in.readLong();
+ long energyConsumedMaMs = in.readLong();
+ long[] timeInRatMs = in.createLongArray();
+ long[] timeInRxSignalStrengthLevelMs = in.createLongArray();
+ long[] txTimeMs = in.createLongArray();
+ long monitoredRailChargeConsumedMaMs = in.readLong();
+
+ return new CellularBatteryStats(loggingDurationMs, kernelActiveTimeMs,
+ numPacketsTx, numBytesTx, numPacketsRx, numBytesRx, sleepTimeMs,
+ idleTimeMs, rxTimeMs, energyConsumedMaMs, timeInRatMs,
+ timeInRxSignalStrengthLevelMs, txTimeMs,
+ monitoredRailChargeConsumedMaMs);
+ }
+
+ public CellularBatteryStats[] newArray(int size) {
+ return new CellularBatteryStats[size];
+ }
+ };
+
+ /** @hide **/
+ public CellularBatteryStats(long loggingDurationMs, long kernelActiveTimeMs, long numPacketsTx,
+ long numBytesTx, long numPacketsRx, long numBytesRx, long sleepTimeMs, long idleTimeMs,
+ long rxTimeMs, Long energyConsumedMaMs, long[] timeInRatMs,
+ long[] timeInRxSignalStrengthLevelMs, long[] txTimeMs,
+ long monitoredRailChargeConsumedMaMs) {
+
+ mLoggingDurationMs = loggingDurationMs;
+ mKernelActiveTimeMs = kernelActiveTimeMs;
+ mNumPacketsTx = numPacketsTx;
+ mNumBytesTx = numBytesTx;
+ mNumPacketsRx = numPacketsRx;
+ mNumBytesRx = numBytesRx;
+ mSleepTimeMs = sleepTimeMs;
+ mIdleTimeMs = idleTimeMs;
+ mRxTimeMs = rxTimeMs;
+ mEnergyConsumedMaMs = energyConsumedMaMs;
+ mTimeInRatMs = Arrays.copyOfRange(
+ timeInRatMs, 0,
+ Math.min(timeInRatMs.length, BatteryStats.NUM_DATA_CONNECTION_TYPES));
+ mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(
+ timeInRxSignalStrengthLevelMs, 0,
+ Math.min(timeInRxSignalStrengthLevelMs.length,
+ CellSignalStrength.getNumSignalStrengthLevels()));
+ mTxTimeMs = Arrays.copyOfRange(
+ txTimeMs, 0,
+ Math.min(txTimeMs.length, ModemActivityInfo.getNumTxPowerLevels()));
+ mMonitoredRailChargeConsumedMaMs = monitoredRailChargeConsumedMaMs;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull 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);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof CellularBatteryStats)) return false;
+ if (other == this) return true;
+ CellularBatteryStats otherStats = (CellularBatteryStats) other;
+ return this.mLoggingDurationMs == otherStats.mLoggingDurationMs
+ && this.mKernelActiveTimeMs == otherStats.mKernelActiveTimeMs
+ && this.mNumPacketsTx == otherStats.mNumPacketsTx
+ && this.mNumBytesTx == otherStats.mNumBytesTx
+ && this.mNumPacketsRx == otherStats.mNumPacketsRx
+ && this.mNumBytesRx == otherStats.mNumBytesRx
+ && this.mSleepTimeMs == otherStats.mSleepTimeMs
+ && this.mIdleTimeMs == otherStats.mIdleTimeMs
+ && this.mRxTimeMs == otherStats.mRxTimeMs
+ && this.mEnergyConsumedMaMs == otherStats.mEnergyConsumedMaMs
+ && Arrays.equals(this.mTimeInRatMs, otherStats.mTimeInRatMs)
+ && Arrays.equals(this.mTimeInRxSignalStrengthLevelMs,
+ otherStats.mTimeInRxSignalStrengthLevelMs)
+ && Arrays.equals(this.mTxTimeMs, otherStats.mTxTimeMs)
+ && this.mMonitoredRailChargeConsumedMaMs
+ == otherStats.mMonitoredRailChargeConsumedMaMs;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLoggingDurationMs, mKernelActiveTimeMs, mNumPacketsTx,
+ mNumBytesTx, mNumPacketsRx, mNumBytesRx, mSleepTimeMs, mIdleTimeMs,
+ mRxTimeMs, mEnergyConsumedMaMs, Arrays.hashCode(mTimeInRatMs),
+ Arrays.hashCode(mTimeInRxSignalStrengthLevelMs), Arrays.hashCode(mTxTimeMs),
+ mMonitoredRailChargeConsumedMaMs);
+ }
+
+ /**
+ * Returns the duration for which these cellular stats were collected.
+ *
+ * @return Duration of stats collection in milliseconds.
+ */
+ public long getLoggingDurationMillis() {
+ return mLoggingDurationMs;
+ }
+
+ /**
+ * Returns the duration for which the kernel was active within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of kernel active time in milliseconds.
+ */
+ public long getKernelActiveTimeMillis() {
+ return mKernelActiveTimeMs;
+ }
+
+ /**
+ * Returns the number of packets transmitted over cellular within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of packets transmitted.
+ */
+ public long getNumPacketsTx() {
+ return mNumPacketsTx;
+ }
+
+ /**
+ * Returns the number of packets received over cellular within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of packets received.
+ */
+ public long getNumBytesTx() {
+ return mNumBytesTx;
+ }
+
+ /**
+ * Returns the number of bytes transmitted over cellular within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of bytes transmitted.
+ */
+ public long getNumPacketsRx() {
+ return mNumPacketsRx;
+ }
+
+ /**
+ * Returns the number of bytes received over cellular within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of bytes received.
+ */
+ public long getNumBytesRx() {
+ return mNumBytesRx;
+ }
+
+ /**
+ * Returns the duration for which the device was sleeping within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of sleep time in milliseconds.
+ */
+ public long getSleepTimeMillis() {
+ return mSleepTimeMs;
+ }
+
+ /**
+ * Returns the duration for which the device was idle within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of idle time in milliseconds.
+ */
+ public long getIdleTimeMillis() {
+ return mIdleTimeMs;
+ }
+
+ /**
+ * Returns the duration for which the device was receiving over cellular within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of cellular reception time in milliseconds.
+ */
+ public long getRxTimeMillis() {
+ return mRxTimeMs;
+ }
+
+ /**
+ * Returns an estimation of energy consumed by cellular chip within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Energy consumed in milli-ampere milliseconds (mAmS).
+ */
+ public long getEnergyConsumedMaMillis() {
+ return mEnergyConsumedMaMs;
+ }
+
+ /**
+ * Returns the time in microseconds that the phone has been running with
+ * the given data connection.
+ *
+ * @param networkType The network type to query.
+ * @return The amount of time the phone spends in the {@code networkType} network type. The
+ * unit is in microseconds.
+ */
+ @NonNull
+ @SuppressLint("MethodNameUnits")
+ public long getTimeInRatMicros(@NetworkType int networkType) {
+ if (networkType >= mTimeInRatMs.length) {
+ return -1;
+ }
+
+ return mTimeInRatMs[networkType];
+ }
+
+ /**
+ * Returns the time in microseconds that the phone has been running with
+ * the given signal strength.
+ *
+ * @param signalStrengthBin a single integer from 0 to 4 representing the general signal
+ * quality.
+ * @return Amount of time phone spends in specific cellular rx signal strength levels
+ * in microseconds. The index is signal strength bin.
+ */
+ @NonNull
+ @SuppressLint("MethodNameUnits")
+ public long getTimeInRxSignalStrengthLevelMicros(
+ @IntRange(from = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+ to = CellSignalStrength.SIGNAL_STRENGTH_GREAT) int signalStrengthBin) {
+ if (signalStrengthBin >= mTimeInRxSignalStrengthLevelMs.length) {
+ return -1;
+ }
+ return mTimeInRxSignalStrengthLevelMs[signalStrengthBin];
+ }
+
+ /**
+ * Returns the duration for which the device was transmitting over cellular within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @param level a single integer from 0 to 4 representing the Tx(transmit) power level.
+ * @return Duration of cellular transmission time for specific power level in milliseconds.
+ *
+ * Tx(transmit) power level. see power index @ModemActivityInfo.TxPowerLevel below
+ * <ul>
+ * <li> index 0 = tx_power < 0dBm. </li>
+ * <li> index 1 = 0dBm < tx_power < 5dBm. </li>
+ * <li> index 2 = 5dBm < tx_power < 15dBm. </li>
+ * <li> index 3 = 15dBm < tx_power < 20dBm. </li>
+ * <li> index 4 = tx_power > 20dBm. </li>
+ * </ul>
+ *
+ * @hide
+ */
+ @NonNull
+ public long getTxTimeMillis(
+ @IntRange(from = ModemActivityInfo.TX_POWER_LEVEL_0,
+ to = ModemActivityInfo.TX_POWER_LEVEL_4) int level) {
+ if (level >= mTxTimeMs.length) {
+ return -1;
+ }
+
+ return mTxTimeMs[level];
+ }
+
+ /**
+ * Returns the energy consumed by cellular chip within {@link #getLoggingDurationMillis()}.
+ *
+ * @return Energy consumed in milli-ampere milli-seconds (mAmS).
+ */
+ public long getMonitoredRailChargeConsumedMaMillis() {
+ return mMonitoredRailChargeConsumedMaMs;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android-34/android/os/connectivity/GpsBatteryStats.java b/android-34/android/os/connectivity/GpsBatteryStats.java
new file mode 100644
index 0000000..5e21bd3
--- /dev/null
+++ b/android-34/android/os/connectivity/GpsBatteryStats.java
@@ -0,0 +1,107 @@
+/*
+ * 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.location.GnssSignalQuality;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+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, GnssSignalQuality.NUM_GNSS_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[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
+ return;
+ }
+}
\ No newline at end of file
diff --git a/android-34/android/os/connectivity/WifiActivityEnergyInfo.java b/android-34/android/os/connectivity/WifiActivityEnergyInfo.java
new file mode 100644
index 0000000..ad74a9f
--- /dev/null
+++ b/android-34/android/os/connectivity/WifiActivityEnergyInfo.java
@@ -0,0 +1,247 @@
+/*
+ * 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.connectivity;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.os.PowerProfile;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Record of energy and activity information from controller and
+ * underlying wifi stack state. Timestamp the record with elapsed
+ * real-time.
+ * @hide
+ */
+@SystemApi
+public final class WifiActivityEnergyInfo implements Parcelable {
+ @ElapsedRealtimeLong
+ private final long mTimeSinceBootMillis;
+ @StackState
+ private final int mStackState;
+ @IntRange(from = 0)
+ private final long mControllerTxDurationMillis;
+ @IntRange(from = 0)
+ private final long mControllerRxDurationMillis;
+ @IntRange(from = 0)
+ private final long mControllerScanDurationMillis;
+ @IntRange(from = 0)
+ private final long mControllerIdleDurationMillis;
+ @IntRange(from = 0)
+ private final long mControllerEnergyUsedMicroJoules;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"STACK_STATE_"}, value = {
+ STACK_STATE_INVALID,
+ STACK_STATE_STATE_ACTIVE,
+ STACK_STATE_STATE_SCANNING,
+ STACK_STATE_STATE_IDLE})
+ public @interface StackState {}
+
+ /** Invalid Wifi stack state. */
+ public static final int STACK_STATE_INVALID = 0;
+ /** Wifi stack is active. */
+ public static final int STACK_STATE_STATE_ACTIVE = 1;
+ /** Wifi stack is scanning. */
+ public static final int STACK_STATE_STATE_SCANNING = 2;
+ /** Wifi stack is idle. */
+ public static final int STACK_STATE_STATE_IDLE = 3;
+
+ /**
+ * Constructor.
+ *
+ * @param timeSinceBootMillis the elapsed real time since boot, in milliseconds.
+ * @param stackState The current state of the Wifi Stack. One of {@link #STACK_STATE_INVALID},
+ * {@link #STACK_STATE_STATE_ACTIVE}, {@link #STACK_STATE_STATE_SCANNING},
+ * or {@link #STACK_STATE_STATE_IDLE}.
+ * @param txDurationMillis Cumulative milliseconds of active transmission.
+ * @param rxDurationMillis Cumulative milliseconds of active receive.
+ * @param scanDurationMillis Cumulative milliseconds when radio is awake due to scan.
+ * @param idleDurationMillis Cumulative milliseconds when radio is awake but not transmitting or
+ * receiving.
+ */
+ public WifiActivityEnergyInfo(
+ @ElapsedRealtimeLong long timeSinceBootMillis,
+ @StackState int stackState,
+ @IntRange(from = 0) long txDurationMillis,
+ @IntRange(from = 0) long rxDurationMillis,
+ @IntRange(from = 0) long scanDurationMillis,
+ @IntRange(from = 0) long idleDurationMillis) {
+
+ this(timeSinceBootMillis,
+ stackState,
+ txDurationMillis,
+ rxDurationMillis,
+ scanDurationMillis,
+ idleDurationMillis,
+ calculateEnergyMicroJoules(txDurationMillis, rxDurationMillis, idleDurationMillis));
+ }
+
+ private static long calculateEnergyMicroJoules(
+ long txDurationMillis, long rxDurationMillis, long idleDurationMillis) {
+ final Context context = ActivityThread.currentActivityThread().getSystemContext();
+ if (context == null) {
+ return 0L;
+ }
+ // Calculate energy used using PowerProfile.
+ PowerProfile powerProfile = new PowerProfile(context);
+ final double idleCurrent = powerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
+ final double rxCurrent = powerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_RX);
+ final double txCurrent = powerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_TX);
+ final double voltage = powerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+
+ return (long) ((txDurationMillis * txCurrent
+ + rxDurationMillis * rxCurrent
+ + idleDurationMillis * idleCurrent)
+ * voltage);
+ }
+
+ /** @hide */
+ public WifiActivityEnergyInfo(
+ @ElapsedRealtimeLong long timeSinceBootMillis,
+ @StackState int stackState,
+ @IntRange(from = 0) long txDurationMillis,
+ @IntRange(from = 0) long rxDurationMillis,
+ @IntRange(from = 0) long scanDurationMillis,
+ @IntRange(from = 0) long idleDurationMillis,
+ @IntRange(from = 0) long energyUsedMicroJoules) {
+ mTimeSinceBootMillis = timeSinceBootMillis;
+ mStackState = stackState;
+ mControllerTxDurationMillis = txDurationMillis;
+ mControllerRxDurationMillis = rxDurationMillis;
+ mControllerScanDurationMillis = scanDurationMillis;
+ mControllerIdleDurationMillis = idleDurationMillis;
+ mControllerEnergyUsedMicroJoules = energyUsedMicroJoules;
+ }
+
+ @Override
+ public String toString() {
+ return "WifiActivityEnergyInfo{"
+ + " mTimeSinceBootMillis=" + mTimeSinceBootMillis
+ + " mStackState=" + mStackState
+ + " mControllerTxDurationMillis=" + mControllerTxDurationMillis
+ + " mControllerRxDurationMillis=" + mControllerRxDurationMillis
+ + " mControllerScanDurationMillis=" + mControllerScanDurationMillis
+ + " mControllerIdleDurationMillis=" + mControllerIdleDurationMillis
+ + " mControllerEnergyUsedMicroJoules=" + mControllerEnergyUsedMicroJoules
+ + " }";
+ }
+
+ public static final @NonNull Parcelable.Creator<WifiActivityEnergyInfo> CREATOR =
+ new Parcelable.Creator<WifiActivityEnergyInfo>() {
+ public WifiActivityEnergyInfo createFromParcel(Parcel in) {
+ long timestamp = in.readLong();
+ int stackState = in.readInt();
+ long txTime = in.readLong();
+ long rxTime = in.readLong();
+ long scanTime = in.readLong();
+ long idleTime = in.readLong();
+ return new WifiActivityEnergyInfo(timestamp, stackState,
+ txTime, rxTime, scanTime, idleTime);
+ }
+ public WifiActivityEnergyInfo[] newArray(int size) {
+ return new WifiActivityEnergyInfo[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeLong(mTimeSinceBootMillis);
+ out.writeInt(mStackState);
+ out.writeLong(mControllerTxDurationMillis);
+ out.writeLong(mControllerRxDurationMillis);
+ out.writeLong(mControllerScanDurationMillis);
+ out.writeLong(mControllerIdleDurationMillis);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Get the timestamp (elapsed real time milliseconds since boot) of record creation. */
+ @ElapsedRealtimeLong
+ public long getTimeSinceBootMillis() {
+ return mTimeSinceBootMillis;
+ }
+
+ /**
+ * Get the Wifi stack reported state. One of {@link #STACK_STATE_INVALID},
+ * {@link #STACK_STATE_STATE_ACTIVE}, {@link #STACK_STATE_STATE_SCANNING},
+ * {@link #STACK_STATE_STATE_IDLE}.
+ */
+ @StackState
+ public int getStackState() {
+ return mStackState;
+ }
+
+ /** Get the Wifi transmission duration, in milliseconds. */
+ @IntRange(from = 0)
+ public long getControllerTxDurationMillis() {
+ return mControllerTxDurationMillis;
+ }
+
+ /** Get the Wifi receive duration, in milliseconds. */
+ @IntRange(from = 0)
+ public long getControllerRxDurationMillis() {
+ return mControllerRxDurationMillis;
+ }
+
+ /** Get the Wifi scan duration, in milliseconds. */
+ @IntRange(from = 0)
+ public long getControllerScanDurationMillis() {
+ return mControllerScanDurationMillis;
+ }
+
+ /** Get the Wifi idle duration, in milliseconds. */
+ @IntRange(from = 0)
+ public long getControllerIdleDurationMillis() {
+ return mControllerIdleDurationMillis;
+ }
+
+ /** Get the energy consumed by Wifi, in microjoules. */
+ @IntRange(from = 0)
+ public long getControllerEnergyUsedMicroJoules() {
+ return mControllerEnergyUsedMicroJoules;
+ }
+
+ /**
+ * Returns true if the record is valid, false otherwise.
+ * @hide
+ */
+ public boolean isValid() {
+ return mControllerTxDurationMillis >= 0
+ && mControllerRxDurationMillis >= 0
+ && mControllerScanDurationMillis >= 0
+ && mControllerIdleDurationMillis >= 0;
+ }
+}
diff --git a/android-34/android/os/connectivity/WifiBatteryStats.java b/android-34/android/os/connectivity/WifiBatteryStats.java
new file mode 100644
index 0000000..7e6ebcf
--- /dev/null
+++ b/android-34/android/os/connectivity/WifiBatteryStats.java
@@ -0,0 +1,321 @@
+/*
+ * 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 static android.os.BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS;
+import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
+import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Class for holding Wifi related battery stats
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiBatteryStats implements Parcelable {
+ private final long mLoggingDurationMillis;
+ private final long mKernelActiveTimeMillis;
+ private final long mNumPacketsTx;
+ private final long mNumBytesTx;
+ private final long mNumPacketsRx;
+ private final long mNumBytesRx;
+ private final long mSleepTimeMillis;
+ private final long mScanTimeMillis;
+ private final long mIdleTimeMillis;
+ private final long mRxTimeMillis;
+ private final long mTxTimeMillis;
+ private final long mEnergyConsumedMaMillis;
+ private final long mAppScanRequestCount;
+ private final long[] mTimeInStateMillis;
+ private final long[] mTimeInSupplicantStateMillis;
+ private final long[] mTimeInRxSignalStrengthLevelMillis;
+ private final long mMonitoredRailChargeConsumedMaMillis;
+
+ public static final @NonNull Parcelable.Creator<WifiBatteryStats> CREATOR =
+ new Parcelable.Creator<WifiBatteryStats>() {
+ public WifiBatteryStats createFromParcel(Parcel in) {
+ long loggingDurationMillis = in.readLong();
+ long kernelActiveTimeMillis = in.readLong();
+ long numPacketsTx = in.readLong();
+ long numBytesTx = in.readLong();
+ long numPacketsRx = in.readLong();
+ long numBytesRx = in.readLong();
+ long sleepTimeMillis = in.readLong();
+ long scanTimeMillis = in.readLong();
+ long idleTimeMillis = in.readLong();
+ long rxTimeMillis = in.readLong();
+ long txTimeMillis = in.readLong();
+ long energyConsumedMaMillis = in.readLong();
+ long appScanRequestCount = in.readLong();
+ long[] timeInStateMillis = in.createLongArray();
+ long[] timeInRxSignalStrengthLevelMillis = in.createLongArray();
+ long[] timeInSupplicantStateMillis = in.createLongArray();
+ long monitoredRailChargeConsumedMaMillis = in.readLong();
+ return new WifiBatteryStats(loggingDurationMillis, kernelActiveTimeMillis,
+ numPacketsTx, numBytesTx, numPacketsRx, numBytesRx, sleepTimeMillis,
+ scanTimeMillis, idleTimeMillis, rxTimeMillis, txTimeMillis,
+ energyConsumedMaMillis, appScanRequestCount, timeInStateMillis,
+ timeInRxSignalStrengthLevelMillis, timeInSupplicantStateMillis,
+ monitoredRailChargeConsumedMaMillis);
+ }
+
+ public WifiBatteryStats[] newArray(int size) {
+ return new WifiBatteryStats[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeLong(mLoggingDurationMillis);
+ out.writeLong(mKernelActiveTimeMillis);
+ out.writeLong(mNumPacketsTx);
+ out.writeLong(mNumBytesTx);
+ out.writeLong(mNumPacketsRx);
+ out.writeLong(mNumBytesRx);
+ out.writeLong(mSleepTimeMillis);
+ out.writeLong(mScanTimeMillis);
+ out.writeLong(mIdleTimeMillis);
+ out.writeLong(mRxTimeMillis);
+ out.writeLong(mTxTimeMillis);
+ out.writeLong(mEnergyConsumedMaMillis);
+ out.writeLong(mAppScanRequestCount);
+ out.writeLongArray(mTimeInStateMillis);
+ out.writeLongArray(mTimeInRxSignalStrengthLevelMillis);
+ out.writeLongArray(mTimeInSupplicantStateMillis);
+ out.writeLong(mMonitoredRailChargeConsumedMaMillis);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof WifiBatteryStats)) return false;
+ if (other == this) return true;
+ WifiBatteryStats otherStats = (WifiBatteryStats) other;
+ return this.mLoggingDurationMillis == otherStats.mLoggingDurationMillis
+ && this.mKernelActiveTimeMillis == otherStats.mKernelActiveTimeMillis
+ && this.mNumPacketsTx == otherStats.mNumPacketsTx
+ && this.mNumBytesTx == otherStats.mNumBytesTx
+ && this.mNumPacketsRx == otherStats.mNumPacketsRx
+ && this.mNumBytesRx == otherStats.mNumBytesRx
+ && this.mSleepTimeMillis == otherStats.mSleepTimeMillis
+ && this.mScanTimeMillis == otherStats.mScanTimeMillis
+ && this.mIdleTimeMillis == otherStats.mIdleTimeMillis
+ && this.mRxTimeMillis == otherStats.mRxTimeMillis
+ && this.mTxTimeMillis == otherStats.mTxTimeMillis
+ && this.mEnergyConsumedMaMillis == otherStats.mEnergyConsumedMaMillis
+ && this.mAppScanRequestCount == otherStats.mAppScanRequestCount
+ && Arrays.equals(this.mTimeInStateMillis, otherStats.mTimeInStateMillis)
+ && Arrays.equals(this.mTimeInSupplicantStateMillis,
+ otherStats.mTimeInSupplicantStateMillis)
+ && Arrays.equals(this.mTimeInRxSignalStrengthLevelMillis,
+ otherStats.mTimeInRxSignalStrengthLevelMillis)
+ && this.mMonitoredRailChargeConsumedMaMillis
+ == otherStats.mMonitoredRailChargeConsumedMaMillis;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLoggingDurationMillis, mKernelActiveTimeMillis, mNumPacketsTx,
+ mNumBytesTx, mNumPacketsRx, mNumBytesRx, mSleepTimeMillis, mScanTimeMillis,
+ mIdleTimeMillis, mRxTimeMillis, mTxTimeMillis, mEnergyConsumedMaMillis,
+ mAppScanRequestCount, Arrays.hashCode(mTimeInStateMillis),
+ Arrays.hashCode(mTimeInSupplicantStateMillis),
+ Arrays.hashCode(mTimeInRxSignalStrengthLevelMillis),
+ mMonitoredRailChargeConsumedMaMillis);
+ }
+
+ /** @hide **/
+ public WifiBatteryStats(long loggingDurationMillis, long kernelActiveTimeMillis,
+ long numPacketsTx, long numBytesTx, long numPacketsRx, long numBytesRx,
+ long sleepTimeMillis, long scanTimeMillis, long idleTimeMillis, long rxTimeMillis,
+ long txTimeMillis, long energyConsumedMaMillis, long appScanRequestCount,
+ @NonNull long[] timeInStateMillis, @NonNull long [] timeInRxSignalStrengthLevelMillis,
+ @NonNull long[] timeInSupplicantStateMillis, long monitoredRailChargeConsumedMaMillis) {
+ mLoggingDurationMillis = loggingDurationMillis;
+ mKernelActiveTimeMillis = kernelActiveTimeMillis;
+ mNumPacketsTx = numPacketsTx;
+ mNumBytesTx = numBytesTx;
+ mNumPacketsRx = numPacketsRx;
+ mNumBytesRx = numBytesRx;
+ mSleepTimeMillis = sleepTimeMillis;
+ mScanTimeMillis = scanTimeMillis;
+ mIdleTimeMillis = idleTimeMillis;
+ mRxTimeMillis = rxTimeMillis;
+ mTxTimeMillis = txTimeMillis;
+ mEnergyConsumedMaMillis = energyConsumedMaMillis;
+ mAppScanRequestCount = appScanRequestCount;
+ mTimeInStateMillis = Arrays.copyOfRange(
+ timeInStateMillis, 0,
+ Math.min(timeInStateMillis.length, NUM_WIFI_STATES));
+ mTimeInRxSignalStrengthLevelMillis = Arrays.copyOfRange(
+ timeInRxSignalStrengthLevelMillis, 0,
+ Math.min(timeInRxSignalStrengthLevelMillis.length, NUM_WIFI_SIGNAL_STRENGTH_BINS));
+ mTimeInSupplicantStateMillis = Arrays.copyOfRange(
+ timeInSupplicantStateMillis, 0,
+ Math.min(timeInSupplicantStateMillis.length, NUM_WIFI_SUPPL_STATES));
+ mMonitoredRailChargeConsumedMaMillis = monitoredRailChargeConsumedMaMillis;
+ }
+
+ /**
+ * Returns the duration for which these wifi stats were collected.
+ *
+ * @return Duration of stats collection in millis.
+ */
+ public long getLoggingDurationMillis() {
+ return mLoggingDurationMillis;
+ }
+
+ /**
+ * Returns the duration for which the kernel was active within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of kernel active time in millis.
+ */
+ public long getKernelActiveTimeMillis() {
+ return mKernelActiveTimeMillis;
+ }
+
+ /**
+ * Returns the number of packets transmitted over wifi within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of packets transmitted.
+ */
+ public long getNumPacketsTx() {
+ return mNumPacketsTx;
+ }
+
+ /**
+ * Returns the number of bytes transmitted over wifi within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of bytes transmitted.
+ */
+ public long getNumBytesTx() {
+ return mNumBytesTx;
+ }
+
+ /**
+ * Returns the number of packets received over wifi within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of packets received.
+ */
+ public long getNumPacketsRx() {
+ return mNumPacketsRx;
+ }
+
+ /**
+ * Returns the number of bytes received over wifi within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of bytes received.
+ */
+ public long getNumBytesRx() {
+ return mNumBytesRx;
+ }
+
+ /**
+ * Returns the duration for which the device was sleeping within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of sleep time in millis.
+ */
+ public long getSleepTimeMillis() {
+ return mSleepTimeMillis;
+ }
+
+ /**
+ * Returns the duration for which the device was wifi scanning within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of wifi scanning time in millis.
+ */
+ public long getScanTimeMillis() {
+ return mScanTimeMillis;
+ }
+
+ /**
+ * Returns the duration for which the device was idle within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of idle time in millis.
+ */
+ public long getIdleTimeMillis() {
+ return mIdleTimeMillis;
+ }
+
+ /**
+ * Returns the duration for which the device was receiving over wifi within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of wifi reception time in millis.
+ */
+ public long getRxTimeMillis() {
+ return mRxTimeMillis;
+ }
+
+ /**
+ * Returns the duration for which the device was transmitting over wifi within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Duration of wifi transmission time in millis.
+ */
+ public long getTxTimeMillis() {
+ return mTxTimeMillis;
+ }
+
+ /**
+ * Returns an estimation of energy consumed in millis by wifi chip within
+ * {@link #getLoggingDurationMillis()}.
+ *
+ * @return Energy consumed in millis.
+ */
+ public long getEnergyConsumedMaMillis() {
+ return mEnergyConsumedMaMillis;
+ }
+
+ /**
+ * Returns the number of app initiated wifi scans within {@link #getLoggingDurationMillis()}.
+ *
+ * @return Number of app scans.
+ */
+ public long getAppScanRequestCount() {
+ return mAppScanRequestCount;
+ }
+
+ /**
+ * Returns the energy consumed by wifi chip within {@link #getLoggingDurationMillis()}.
+ *
+ * @return Energy consumed in millis.
+ */
+ public long getMonitoredRailChargeConsumedMaMillis() {
+ return mMonitoredRailChargeConsumedMaMillis;
+ }
+}
diff --git a/android-34/android/os/health/HealthKeys.java b/android-34/android/os/health/HealthKeys.java
new file mode 100644
index 0000000..5d60411
--- /dev/null
+++ b/android-34/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-34/android/os/health/HealthStats.java b/android-34/android/os/health/HealthStats.java
new file mode 100644
index 0000000..6c648f1
--- /dev/null
+++ b/android-34/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 single {@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-34/android/os/health/HealthStatsParceler.java b/android-34/android/os/health/HealthStatsParceler.java
new file mode 100644
index 0000000..eb864a4
--- /dev/null
+++ b/android-34/android/os/health/HealthStatsParceler.java
@@ -0,0 +1,88 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/health/HealthStatsWriter.java b/android-34/android/os/health/HealthStatsWriter.java
new file mode 100644
index 0000000..d4d10b0
--- /dev/null
+++ b/android-34/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-34/android/os/health/PackageHealthStats.java b/android-34/android/os/health/PackageHealthStats.java
new file mode 100644
index 0000000..fb52cb6
--- /dev/null
+++ b/android-34/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-34/android/os/health/PidHealthStats.java b/android-34/android/os/health/PidHealthStats.java
new file mode 100644
index 0000000..0d2a3f1
--- /dev/null
+++ b/android-34/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-34/android/os/health/ProcessHealthStats.java b/android-34/android/os/health/ProcessHealthStats.java
new file mode 100644
index 0000000..b3f0dfc
--- /dev/null
+++ b/android-34/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-34/android/os/health/ServiceHealthStats.java b/android-34/android/os/health/ServiceHealthStats.java
new file mode 100644
index 0000000..cc48b3e
--- /dev/null
+++ b/android-34/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-34/android/os/health/SystemHealthManager.java b/android-34/android/os/health/SystemHealthManager.java
new file mode 100644
index 0000000..8181911
--- /dev/null
+++ b/android-34/android/os/health/SystemHealthManager.java
@@ -0,0 +1,140 @@
+/*
+ * 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.compat.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 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/health/TimerStat.java b/android-34/android/os/health/TimerStat.java
new file mode 100644
index 0000000..4aaa85f
--- /dev/null
+++ b/android-34/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-34/android/os/health/UidHealthStats.java b/android-34/android/os/health/UidHealthStats.java
new file mode 100644
index 0000000..488a542
--- /dev/null
+++ b/android-34/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 only 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 only 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 partial 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-34/android/os/image/DynamicSystemClient.java b/android-34/android/os/image/DynamicSystemClient.java
new file mode 100644
index 0000000..218ecc8
--- /dev/null
+++ b/android-34/android/os/image/DynamicSystemClient.java
@@ -0,0 +1,463 @@
+/*
+ * 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.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.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
+public class DynamicSystemClient {
+ private static final String TAG = "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 {}
+
+ /** 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 action: hide notifications about the status of {@code DynamicSystem}.
+ * @hide
+ */
+ public static final String ACTION_HIDE_NOTIFICATION =
+ "android.os.image.action.HIDE_NOTIFICATION";
+
+ /*
+ * 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";
+
+ /**
+ * Intent key: Whether to enable DynamicSystem immediately after installation is done.
+ * Note this will reboot the device automatically.
+ * @hide
+ */
+ public static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+
+ /**
+ * Intent key: Whether to leave DynamicSystem on device reboot.
+ * False indicates a sticky mode where device stays in DynamicSystem across reboots.
+ * @hide
+ */
+ public static final String KEY_ONE_SHOT = "KEY_ONE_SHOT";
+
+ /**
+ * Intent key: Whether to use default strings when showing the dialog that prompts
+ * user for device credentials.
+ * False indicates using the custom strings provided by {@code DynamicSystem}.
+ * @hide
+ */
+ public static final String KEY_KEYGUARD_USE_DEFAULT_STRINGS =
+ "KEY_KEYGUARD_USE_DEFAULT_STRINGS";
+
+ 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, "onServiceConnected: " + className);
+
+ 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");
+ notifyOnStatusChangedListener(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Slog.v(TAG, "onServiceDisconnected: " + className);
+ 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
+ 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;
+ }
+
+ private void notifyOnStatusChangedListener(
+ int status, int cause, long progress, Throwable detail) {
+ if (mListener != null) {
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> {
+ mListener.onStatusChanged(status, cause, progress, detail);
+ });
+ } else {
+ mListener.onStatusChanged(status, cause, progress, detail);
+ }
+ }
+ }
+
+ /**
+ * 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
+ public void bind() {
+ 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
+ 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
+ public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) {
+ start(systemUrl, systemSize, 0 /* Use the 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) {
+ Intent intent = new Intent();
+
+ intent.setClassName("com.android.dynsystem",
+ "com.android.dynsystem.VerificationActivity");
+
+ intent.setData(systemUrl);
+ intent.setAction(ACTION_START_INSTALL);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ intent.putExtra(KEY_SYSTEM_SIZE, systemSize);
+ intent.putExtra(KEY_USERDATA_SIZE, userdataSize);
+
+ mContext.startActivity(intent);
+ }
+
+ 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, android.os.ParcelableException.class);
+
+ Throwable detail = t == null ? null : t.getCause();
+
+ notifyOnStatusChangedListener(status, cause, progress, detail);
+ break;
+ default:
+ // do nothing
+
+ }
+ }
+}
diff --git a/android-34/android/os/image/DynamicSystemManager.java b/android-34/android/os/image/DynamicSystemManager.java
new file mode 100644
index 0000000..9610b16
--- /dev/null
+++ b/android-34/android/os/image/DynamicSystemManager.java
@@ -0,0 +1,288 @@
+/*
+ * 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.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.gsi.AvbPublicKey;
+import android.gsi.GsiProgress;
+import android.gsi.IGsiService;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Pair;
+
+/**
+ * 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() {}
+
+ /**
+ * Set the file descriptor that points to a ashmem which will be used
+ * to fetch data during the submitFromAshmem.
+ *
+ * @param ashmem fd that points to a ashmem
+ * @param size size of the ashmem file
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
+ try {
+ return mService.setAshmem(ashmem, size);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Submit bytes to the DSU partition from the ashmem previously set with
+ * setAshmem.
+ *
+ * @param size Number of bytes
+ * @return true on success, false otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean submitFromAshmem(int size) {
+ try {
+ return mService.submitFromAshmem(size);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Retrieve AVB public key from installing partition.
+ *
+ * @param dst Output the AVB public key.
+ * @return true on success, false if partition doesn't have a
+ * valid VBMeta block to retrieve the AVB key from.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean getAvbPublicKey(AvbPublicKey dst) {
+ try {
+ return mService.getAvbPublicKey(dst);
+ } 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.setEnable(true, true);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ }
+ /**
+ * Start DynamicSystem installation.
+ *
+ * @return true if the call succeeds
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean startInstallation(String dsuSlot) {
+ try {
+ return mService.startInstallation(dsuSlot);
+ } 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 name The DSU partition name
+ * @param size Size of the DSU image in bytes
+ * @param readOnly True if the partition is read only, e.g. system.
+ * @return {@code Integer} an IGsiService.INSTALL_* status code. {@link Session} an installation
+ * session object if successful, otherwise {@code null}.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public @NonNull Pair<Integer, Session> createPartition(
+ String name, long size, boolean readOnly) {
+ try {
+ int status = mService.createPartition(name, size, readOnly);
+ if (status == IGsiService.INSTALL_OK) {
+ return new Pair<>(status, new Session());
+ } else {
+ return new Pair<>(status, null);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ /**
+ * Complete the current partition installation.
+ *
+ * @return true if the partition installation completes without error.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean closePartition() {
+ try {
+ return mService.closePartition();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ /**
+ * Finish a previously started installation. Installations without a cooresponding
+ * finishInstallation() will be cleaned up during device boot.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public boolean finishInstallation() {
+ try {
+ return mService.finishInstallation();
+ } 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, boolean oneShot) {
+ try {
+ return mService.setEnable(enable, oneShot);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Returns the suggested scratch partition size for overlayFS.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public long suggestScratchSize() {
+ try {
+ return mService.suggestScratchSize();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+}
diff --git a/android-34/android/os/incremental/IncrementalFileStorages.java b/android-34/android/os/incremental/IncrementalFileStorages.java
new file mode 100644
index 0000000..6f4a12f
--- /dev/null
+++ b/android-34/android/os/incremental/IncrementalFileStorages.java
@@ -0,0 +1,233 @@
+/*
+ * 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.incremental;
+
+/**
+ * Set up files and directories used in an installation session. Currently only used by Incremental
+ * Installation. For Incremental installation, the expected outcome of this function is: 0) All the
+ * files are in defaultStorage 1) All APK files are in the same directory, bound to mApkStorage, and
+ * bound to the InstallerSession's stage dir. The files are linked from mApkStorage to
+ * defaultStorage. 2) All lib files are in the sub directories as their names suggest, and in the
+ * same parent directory as the APK files. The files are linked from mApkStorage to defaultStorage.
+ * 3) OBB files are in another directory that is different from APK files and lib files, bound to
+ * mObbStorage. The files are linked from mObbStorage to defaultStorage.
+ *
+ * @throws IllegalStateException the session is not an Incremental installation session.
+ */
+
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IPackageLoadingProgressCallback;
+import android.content.pm.InstallationFileParcel;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * This class manages storage instances used during a package installation session.
+ * @hide
+ */
+public final class IncrementalFileStorages {
+ private static final String TAG = "IncrementalFileStorages";
+
+ private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
+
+ private @NonNull final IncrementalManager mIncrementalManager;
+ private @NonNull final File mStageDir;
+ private @Nullable IncrementalStorage mInheritedStorage;
+ private @Nullable IncrementalStorage mDefaultStorage;
+
+ /**
+ * Set up files and directories used in an installation session. Only used by Incremental.
+ * All the files will be created in defaultStorage.
+ *
+ * @throws IllegalStateException the session is not an Incremental installation session.
+ * @throws IOException if fails to setup files or directories.
+ */
+ public static IncrementalFileStorages initialize(Context context,
+ @NonNull File stageDir,
+ @Nullable File inheritedDir,
+ @NonNull DataLoaderParams dataLoaderParams,
+ @Nullable IDataLoaderStatusListener statusListener,
+ @Nullable StorageHealthCheckParams healthCheckParams,
+ @Nullable IStorageHealthListener healthListener,
+ @NonNull List<InstallationFileParcel> addedFiles,
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts,
+ @Nullable IPackageLoadingProgressCallback progressCallback) throws IOException {
+ IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
+ Context.INCREMENTAL_SERVICE);
+ if (incrementalManager == null) {
+ throw new IOException("Failed to obtain incrementalManager.");
+ }
+
+ final IncrementalFileStorages result = new IncrementalFileStorages(stageDir, inheritedDir,
+ incrementalManager, dataLoaderParams);
+ for (InstallationFileParcel file : addedFiles) {
+ if (file.location == LOCATION_DATA_APP) {
+ try {
+ result.addApkFile(file);
+ } catch (IOException e) {
+ throw new IOException(
+ "Failed to add file to IncFS: " + file.name + ", reason: ", e);
+ }
+ } else {
+ throw new IOException("Unknown file location: " + file.location);
+ }
+ }
+ // Register progress loading callback after files have been added
+ if (progressCallback != null) {
+ incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(),
+ progressCallback);
+ }
+ result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener,
+ perUidReadTimeouts);
+
+ return result;
+ }
+
+ private IncrementalFileStorages(@NonNull File stageDir,
+ @Nullable File inheritedDir,
+ @NonNull IncrementalManager incrementalManager,
+ @NonNull DataLoaderParams dataLoaderParams) throws IOException {
+ try {
+ mStageDir = stageDir;
+ mIncrementalManager = incrementalManager;
+ if (inheritedDir != null && IncrementalManager.isIncrementalPath(
+ inheritedDir.getAbsolutePath())) {
+ mInheritedStorage = mIncrementalManager.openStorage(
+ inheritedDir.getAbsolutePath());
+ if (mInheritedStorage != null) {
+ boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals(
+ dataLoaderParams.getComponentName().getPackageName());
+ if (systemDataLoader && !mInheritedStorage.isFullyLoaded()) {
+ // System data loader does not support incomplete storages.
+ throw new IOException("Inherited storage has missing pages.");
+ }
+
+ mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
+ mInheritedStorage, IncrementalManager.CREATE_MODE_CREATE
+ | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
+ if (mDefaultStorage == null) {
+ throw new IOException(
+ "Couldn't create linked incremental storage at " + stageDir);
+ }
+ return;
+ }
+ }
+
+ mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
+ dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE
+ | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
+ if (mDefaultStorage == null) {
+ throw new IOException(
+ "Couldn't create incremental storage at " + stageDir);
+ }
+ } catch (IOException e) {
+ cleanUp();
+ throw e;
+ }
+ }
+
+ private void addApkFile(@NonNull InstallationFileParcel apk) throws IOException {
+ final String apkName = apk.name;
+ final File targetFile = new File(mStageDir, apkName);
+ if (!targetFile.exists()) {
+ mDefaultStorage.makeFile(apkName, apk.size, 0777, null, apk.metadata,
+ apk.signature, null);
+ }
+ }
+
+ /**
+ * Starts or re-starts loading of data.
+ */
+ public void startLoading(
+ @NonNull DataLoaderParams dataLoaderParams,
+ @Nullable IDataLoaderStatusListener statusListener,
+ @Nullable StorageHealthCheckParams healthCheckParams,
+ @Nullable IStorageHealthListener healthListener,
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
+ if (!mDefaultStorage.startLoading(dataLoaderParams, statusListener, healthCheckParams,
+ healthListener, perUidReadTimeouts)) {
+ throw new IOException(
+ "Failed to start or restart loading data for Incremental installation.");
+ }
+ }
+
+ /**
+ * Creates file in default storage and sets its content.
+ */
+ public void makeFile(@NonNull String name, @NonNull byte[] content,
+ @NonNull int mode) throws IOException {
+ mDefaultStorage.makeFile(name, content.length, mode, UUID.randomUUID(),
+ null, null, content);
+ }
+
+ /**
+ * Creates a hardlink from inherited storage to default.
+ */
+ public boolean makeLink(@NonNull String relativePath, @NonNull String fromBase,
+ @NonNull String toBase) throws IOException {
+ if (mInheritedStorage == null) {
+ return false;
+ }
+ final File sourcePath = new File(fromBase, relativePath);
+ final File destPath = new File(toBase, relativePath);
+ mInheritedStorage.makeLink(sourcePath.getAbsolutePath(), mDefaultStorage,
+ destPath.getAbsolutePath());
+ return true;
+ }
+
+ /**
+ * Permanently disables readlogs.
+ */
+ public void disallowReadLogs() {
+ mDefaultStorage.disallowReadLogs();
+ }
+
+ /**
+ * Resets the states and unbinds storage instances for an installation session.
+ */
+ public void cleanUpAndMarkComplete() {
+ IncrementalStorage defaultStorage = cleanUp();
+ if (defaultStorage != null) {
+ defaultStorage.onInstallationComplete();
+ }
+ }
+
+ private IncrementalStorage cleanUp() {
+ IncrementalStorage defaultStorage = mDefaultStorage;
+ mInheritedStorage = null;
+ mDefaultStorage = null;
+ if (defaultStorage == null) {
+ return null;
+ }
+
+ try {
+ mIncrementalManager.unregisterLoadingProgressCallbacks(mStageDir.getAbsolutePath());
+ defaultStorage.unBind(mStageDir.getAbsolutePath());
+ } catch (IOException ignored) {
+ }
+ return defaultStorage;
+ }
+}
diff --git a/android-34/android/os/incremental/IncrementalManager.java b/android-34/android/os/incremental/IncrementalManager.java
new file mode 100644
index 0000000..8004143
--- /dev/null
+++ b/android-34/android/os/incremental/IncrementalManager.java
@@ -0,0 +1,433 @@
+/*
+ * 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.incremental;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.IPackageLoadingProgressCallback;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
+
+/**
+ * Provides operations to open or create an IncrementalStorage, using IIncrementalService
+ * service. Example Usage:
+ *
+ * <blockquote><pre>
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
+ * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
+ * </pre></blockquote>
+ *
+ * @hide
+ */
+@SystemService(Context.INCREMENTAL_SERVICE)
+public final class IncrementalManager {
+ private static final String TAG = "IncrementalManager";
+
+ private static final String ALLOWED_PROPERTY = "incremental.allowed";
+
+ public static final int MIN_VERSION_TO_SUPPORT_FSVERITY = 2;
+
+ public static final int CREATE_MODE_TEMPORARY_BIND =
+ IIncrementalService.CREATE_MODE_TEMPORARY_BIND;
+ public static final int CREATE_MODE_PERMANENT_BIND =
+ IIncrementalService.CREATE_MODE_PERMANENT_BIND;
+ public static final int CREATE_MODE_CREATE =
+ IIncrementalService.CREATE_MODE_CREATE;
+ public static final int CREATE_MODE_OPEN_EXISTING =
+ IIncrementalService.CREATE_MODE_OPEN_EXISTING;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CREATE_MODE_"}, value = {
+ CREATE_MODE_TEMPORARY_BIND,
+ CREATE_MODE_PERMANENT_BIND,
+ CREATE_MODE_CREATE,
+ CREATE_MODE_OPEN_EXISTING,
+ })
+ public @interface CreateMode {
+ }
+
+ private final @Nullable IIncrementalService mService;
+
+ private final LoadingProgressCallbacks mLoadingProgressCallbacks =
+ new LoadingProgressCallbacks();
+
+ public IncrementalManager(IIncrementalService service) {
+ mService = service;
+ }
+
+ /**
+ * Opens or create an Incremental File System mounted directory and returns an
+ * IncrementalStorage object.
+ *
+ * @param path Absolute path to mount Incremental File System on.
+ * @param params IncrementalDataLoaderParams object to configure data loading.
+ * @param createMode Mode for opening an old Incremental File System mount or creating
+ * a new mount.
+ * @return IncrementalStorage object corresponding to the mounted directory.
+ */
+ @Nullable
+ public IncrementalStorage createStorage(@NonNull String path,
+ @NonNull DataLoaderParams params,
+ @CreateMode int createMode) {
+ Objects.requireNonNull(path);
+ Objects.requireNonNull(params);
+ try {
+ final int id = mService.createStorage(path, params.getData(), createMode);
+ if (id < 0) {
+ return null;
+ }
+ return new IncrementalStorage(mService, id);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
+ * object.
+ *
+ * @param path Absolute target path that Incremental File System has been mounted on.
+ * @return IncrementalStorage object corresponding to the mounted directory.
+ */
+ @Nullable
+ public IncrementalStorage openStorage(@NonNull String path) {
+ try {
+ final int id = mService.openStorage(path);
+ if (id < 0) {
+ return null;
+ }
+ final IncrementalStorage storage = new IncrementalStorage(mService, id);
+ return storage;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
+ *
+ * @return IncrementalStorage object corresponding to the linked storage.
+ */
+ @Nullable
+ public IncrementalStorage createStorage(@NonNull String path,
+ @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
+ int id = -1;
+ try {
+ // Incremental service mounts its newly created storage on top of the supplied path,
+ // ensure that the original mode remains the same after mounting.
+ StructStat st = Os.stat(path);
+ id = mService.createLinkedStorage(
+ path, linkedStorage.getId(), createMode);
+ if (id < 0) {
+ return null;
+ }
+ Os.chmod(path, st.st_mode & 07777);
+ return new IncrementalStorage(mService, id);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ErrnoException e) {
+ if (id >= 0) {
+ try {
+ mService.deleteStorage(id);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Link an app's files from the stage dir to the final installation location.
+ * The expected outcome of this method is:
+ * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
+ * of {@code afterCodeFile}.
+ * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
+ *
+ * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
+ * Example: /data/app/vmdl*tmp
+ * @param afterCodeFile Path that should will have APKs after this method is called. Its parent
+ * directory should be bind-mounted to a directory under /data/incremental.
+ * Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
+ * @throws IllegalArgumentException
+ * @throws IOException
+ */
+ public void linkCodePath(File beforeCodeFile, File afterCodeFile)
+ throws IllegalArgumentException, IOException {
+ final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
+ final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
+ if (apkStorage == null) {
+ throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute);
+ }
+ final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent();
+ final IncrementalStorage linkedApkStorage =
+ createStorage(targetStorageDir, apkStorage,
+ IncrementalManager.CREATE_MODE_CREATE
+ | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
+ if (linkedApkStorage == null) {
+ throw new IOException("Failed to create linked storage at dir: " + targetStorageDir);
+ }
+ try {
+ final String afterCodePathName = afterCodeFile.getName();
+ linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
+ } catch (Exception e) {
+ linkedApkStorage.unBind(targetStorageDir);
+ throw e;
+ }
+ }
+
+ /**
+ * Recursively set up directories and link all the files from source storage to target storage.
+ *
+ * @param sourceStorage The storage that has all the files and directories underneath.
+ * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs.
+ * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib".
+ * @param targetStorage The target storage that will have the same files and directories.
+ * @param targetRelativePath The relative path to the directory on the target storage that
+ * should have all the files and dirs underneath,
+ * e.g., "packageName-random".
+ * @throws IOException When makeDirectory or makeLink fails on the Incremental File System.
+ */
+ private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
+ String sourceRelativePath, IncrementalStorage targetStorage,
+ String targetRelativePath) throws IOException {
+ final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath);
+ final Path targetRelative = Paths.get(targetRelativePath);
+ Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
+ throws IOException {
+ final Path relativeDir = sourceBase.relativize(dir);
+ targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString());
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException {
+ final Path relativeFile = sourceBase.relativize(file);
+ sourceStorage.makeLink(
+ file.toAbsolutePath().toString(), targetStorage,
+ targetRelative.resolve(relativeFile).toString());
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ /**
+ * Checks if Incremental feature is enabled on this device.
+ */
+ public static boolean isFeatureEnabled() {
+ return nativeIsEnabled();
+ }
+
+ /**
+ * 0 - IncFs is disabled.
+ * 1 - IncFs v1, core features, no PerUid support. Optional in R.
+ * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
+ */
+ public static int getVersion() {
+ return nativeIsEnabled() ? nativeIsV2Available() ? 2 : 1 : 0;
+ }
+
+ /**
+ * Checks if Incremental installations are allowed.
+ * A developer can disable Incremental installations by setting the property.
+ */
+ public static boolean isAllowed() {
+ return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true);
+ }
+
+ /**
+ * Checks if path is mounted on Incremental File System.
+ */
+ public static boolean isIncrementalPath(@NonNull String path) {
+ return nativeIsIncrementalPath(path);
+ }
+
+ /**
+ * Checks if an fd corresponds to a file on a mounted Incremental File System.
+ */
+ public static boolean isIncrementalFileFd(@NonNull FileDescriptor fd) {
+ return nativeIsIncrementalFd(fd.getInt$());
+ }
+
+ /**
+ * Returns raw signature for file if it's on Incremental File System.
+ * Unsafe, use only if you are sure what you are doing.
+ */
+ public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
+ return nativeUnsafeGetFileSignature(path);
+ }
+
+ /**
+ * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
+ * Unbinds the target dir and deletes the corresponding storage instance.
+ * Deletes the package name and associated storage id from maps.
+ */
+ public void rmPackageDir(@NonNull File codeFile) {
+ try {
+ final String codePath = codeFile.getAbsolutePath();
+ final IncrementalStorage storage = openStorage(codePath);
+ if (storage == null) {
+ return;
+ }
+ mLoadingProgressCallbacks.cleanUpCallbacks(storage);
+ storage.unBind(codePath);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to remove code path", e);
+ }
+ }
+
+ /**
+ * Called when a new callback wants to listen to the loading progress of an installed package.
+ * Increment the count of callbacks associated to the corresponding storage.
+ * Only register storage listener if there hasn't been any existing callback on the storage yet.
+ * @param codePath Path of the installed package. This path is on an Incremental Storage.
+ * @param callback To report loading progress to.
+ * @return True if the package name and associated storage id are valid. False otherwise.
+ */
+ public boolean registerLoadingProgressCallback(@NonNull String codePath,
+ @NonNull IPackageLoadingProgressCallback callback) {
+ final IncrementalStorage storage = openStorage(codePath);
+ if (storage == null) {
+ // storage does not exist, package not installed
+ return false;
+ }
+ return mLoadingProgressCallbacks.registerCallback(storage, callback);
+ }
+
+ /**
+ * Called to stop all listeners from listening to loading progress of an installed package.
+ * @param codePath Path of the installed package
+ */
+ public void unregisterLoadingProgressCallbacks(@NonNull String codePath) {
+ final IncrementalStorage storage = openStorage(codePath);
+ if (storage == null) {
+ // storage does not exist, package not installed
+ return;
+ }
+ mLoadingProgressCallbacks.cleanUpCallbacks(storage);
+ }
+
+ private static class LoadingProgressCallbacks extends IStorageLoadingProgressListener.Stub {
+ @GuardedBy("mCallbacks")
+ private final SparseArray<RemoteCallbackList<IPackageLoadingProgressCallback>> mCallbacks =
+ new SparseArray<>();
+
+ public void cleanUpCallbacks(@NonNull IncrementalStorage storage) {
+ final int storageId = storage.getId();
+ final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage;
+ synchronized (mCallbacks) {
+ callbacksForStorage = mCallbacks.removeReturnOld(storageId);
+ }
+ if (callbacksForStorage == null) {
+ return;
+ }
+ // Unregister all existing callbacks on this storage
+ callbacksForStorage.kill();
+ storage.unregisterLoadingProgressListener();
+ }
+
+ public boolean registerCallback(@NonNull IncrementalStorage storage,
+ @NonNull IPackageLoadingProgressCallback callback) {
+ final int storageId = storage.getId();
+ synchronized (mCallbacks) {
+ RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage =
+ mCallbacks.get(storageId);
+ if (callbacksForStorage == null) {
+ callbacksForStorage = new RemoteCallbackList<>();
+ mCallbacks.put(storageId, callbacksForStorage);
+ }
+ // Registration in RemoteCallbackList needs to be done first, such that when events
+ // come from Incremental Service, the callback is already registered
+ callbacksForStorage.register(callback);
+ if (callbacksForStorage.getRegisteredCallbackCount() > 1) {
+ // already listening for progress for this storage
+ return true;
+ }
+ }
+ return storage.registerLoadingProgressListener(this);
+ }
+
+ @Override
+ public void onStorageLoadingProgressChanged(int storageId, float progress) {
+ final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage;
+ synchronized (mCallbacks) {
+ callbacksForStorage = mCallbacks.get(storageId);
+ }
+ if (callbacksForStorage == null) {
+ // no callback has ever been registered on this storage
+ return;
+ }
+ final int n = callbacksForStorage.beginBroadcast();
+ // RemoteCallbackList use ArrayMap internally and it's safe to iterate this way
+ for (int i = 0; i < n; i++) {
+ final IPackageLoadingProgressCallback callback =
+ callbacksForStorage.getBroadcastItem(i);
+ try {
+ callback.onPackageLoadingProgressChanged(progress);
+ } catch (RemoteException ignored) {
+ }
+ }
+ callbacksForStorage.finishBroadcast();
+ }
+ }
+
+ /**
+ * Returns the metrics of an Incremental Storage.
+ */
+ public IncrementalMetrics getMetrics(@NonNull String codePath) {
+ final IncrementalStorage storage = openStorage(codePath);
+ if (storage == null) {
+ // storage does not exist, package not installed
+ return null;
+ }
+ return new IncrementalMetrics(storage.getMetrics());
+ }
+
+ /* Native methods */
+ private static native boolean nativeIsEnabled();
+ private static native boolean nativeIsV2Available();
+ private static native boolean nativeIsIncrementalPath(@NonNull String path);
+ private static native boolean nativeIsIncrementalFd(@NonNull int fd);
+ private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
+}
diff --git a/android-34/android/os/incremental/IncrementalMetrics.java b/android-34/android/os/incremental/IncrementalMetrics.java
new file mode 100644
index 0000000..534525a
--- /dev/null
+++ b/android-34/android/os/incremental/IncrementalMetrics.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+
+/**
+ * Provides methods to access metrics about an app installed via Incremental
+ * @hide
+ */
+public class IncrementalMetrics {
+ @NonNull private final PersistableBundle mData;
+
+ public IncrementalMetrics(@NonNull PersistableBundle data) {
+ mData = data;
+ }
+
+ /**
+ * @return Milliseconds between now and when the oldest pending read happened
+ */
+ public long getMillisSinceOldestPendingRead() {
+ return mData.getLong(IIncrementalService.METRICS_MILLIS_SINCE_OLDEST_PENDING_READ, -1);
+ }
+
+ /**
+ * @return Whether read logs are enabled
+ */
+ public boolean getReadLogsEnabled() {
+ return mData.getBoolean(IIncrementalService.METRICS_READ_LOGS_ENABLED, false);
+ }
+
+ /**
+ * @return storage health status code. @see android.os.incremental.IStorageHealthListener
+ */
+ public int getStorageHealthStatusCode() {
+ return mData.getInt(IIncrementalService.METRICS_STORAGE_HEALTH_STATUS_CODE, -1);
+ }
+
+ /**
+ * @return data loader status code. @see android.content.pm.IDataLoaderStatusListener
+ */
+ public int getDataLoaderStatusCode() {
+ return mData.getInt(IIncrementalService.METRICS_DATA_LOADER_STATUS_CODE, -1);
+ }
+
+ /**
+ * @return duration since last data loader binding attempt
+ */
+ public long getMillisSinceLastDataLoaderBind() {
+ return mData.getLong(IIncrementalService.METRICS_MILLIS_SINCE_LAST_DATA_LOADER_BIND, -1);
+ }
+
+ /**
+ * @return delay in milliseconds to retry data loader binding
+ */
+ public long getDataLoaderBindDelayMillis() {
+ return mData.getLong(IIncrementalService.METRICS_DATA_LOADER_BIND_DELAY_MILLIS, -1);
+ }
+
+ /**
+ * @return total count of delayed reads caused by pending reads
+ */
+ public int getTotalDelayedReads() {
+ return mData.getInt(IIncrementalService.METRICS_TOTAL_DELAYED_READS, -1);
+ }
+
+ /**
+ * @return total count of failed reads
+ */
+ public int getTotalFailedReads() {
+ return mData.getInt(IIncrementalService.METRICS_TOTAL_FAILED_READS, -1);
+ }
+
+ /**
+ * @return total duration in milliseconds of delayed reads
+ */
+ public long getTotalDelayedReadsDurationMillis() {
+ return mData.getLong(IIncrementalService.METRICS_TOTAL_DELAYED_READS_MILLIS, -1);
+ }
+
+ /**
+ * @return the uid of the last read error
+ */
+ public int getLastReadErrorUid() {
+ return mData.getInt(IIncrementalService.METRICS_LAST_READ_ERROR_UID, -1);
+ }
+
+ /**
+ * @return duration in milliseconds since the last read error
+ */
+ public long getMillisSinceLastReadError() {
+ return mData.getLong(IIncrementalService.METRICS_MILLIS_SINCE_LAST_READ_ERROR, -1);
+ }
+
+ /**
+ * @return the error number of the last read error
+ */
+ public int getLastReadErrorNumber() {
+ return mData.getInt(IIncrementalService.METRICS_LAST_READ_ERROR_NUMBER, -1);
+ }
+}
diff --git a/android-34/android/os/incremental/IncrementalStorage.java b/android-34/android/os/incremental/IncrementalStorage.java
new file mode 100644
index 0000000..9138409
--- /dev/null
+++ b/android-34/android/os/incremental/IncrementalStorage.java
@@ -0,0 +1,604 @@
+/*
+ * 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.incremental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.IDataLoaderStatusListener;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Provides operations on an Incremental File System directory, using IncrementalServiceNative.
+ * Example usage:
+ *
+ * <blockquote><pre>
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
+ * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
+ * storage.makeDirectory("subdir");
+ * </pre></blockquote>
+ *
+ * @hide
+ */
+public final class IncrementalStorage {
+ private static final String TAG = "IncrementalStorage";
+ private final int mId;
+ private final IIncrementalService mService;
+
+
+ public IncrementalStorage(@NonNull IIncrementalService is, int id) {
+ mService = is;
+ mId = id;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Temporarily bind-mounts the current storage directory to a target directory. The bind-mount
+ * will NOT be preserved between device reboots.
+ *
+ * @param targetPath Absolute path to the target directory.
+ */
+ public void bind(@NonNull String targetPath) throws IOException {
+ bind("", targetPath);
+ }
+
+ /**
+ * Temporarily bind-mounts a subdir under the current storage directory to a target directory.
+ * The bind-mount will NOT be preserved between device reboots.
+ *
+ * @param sourcePath Source path as a relative path under current storage
+ * directory.
+ * @param targetPath Absolute path to the target directory.
+ */
+ public void bind(@NonNull String sourcePath, @NonNull String targetPath)
+ throws IOException {
+ try {
+ int res = mService.makeBindMount(mId, sourcePath, targetPath,
+ IIncrementalService.BIND_TEMPORARY);
+ if (res < 0) {
+ throw new IOException("bind() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Permanently bind-mounts the current storage directory to a target directory. The bind-mount
+ * WILL be preserved between device reboots.
+ *
+ * @param targetPath Absolute path to the target directory.
+ */
+ public void bindPermanent(@NonNull String targetPath) throws IOException {
+ bindPermanent("", targetPath);
+ }
+
+ /**
+ * Permanently bind-mounts a subdir under the current storage directory to a target directory.
+ * The bind-mount WILL be preserved between device reboots.
+ *
+ * @param sourcePath Relative path under the current storage directory.
+ * @param targetPath Absolute path to the target directory.
+ */
+ public void bindPermanent(@NonNull String sourcePath, @NonNull String targetPath)
+ throws IOException {
+ try {
+ int res = mService.makeBindMount(mId, sourcePath, targetPath,
+ IIncrementalService.BIND_PERMANENT);
+ if (res < 0) {
+ throw new IOException("bind() permanent failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unbinds a bind mount.
+ *
+ * @param targetPath Absolute path to the target directory.
+ */
+ public void unBind(@NonNull String targetPath) throws IOException {
+ try {
+ int res = mService.deleteBindMount(mId, targetPath);
+ if (res < 0) {
+ throw new IOException("unbind() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a sub-directory under the current storage directory.
+ *
+ * @param path Relative path of the sub-directory, e.g., "subdir"
+ */
+ public void makeDirectory(@NonNull String path) throws IOException {
+ try {
+ int res = mService.makeDirectory(mId, path);
+ if (res < 0) {
+ throw new IOException("makeDirectory() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a sub-directory under the current storage directory. If its parent dirs do not exist,
+ * create the parent dirs as well.
+ *
+ * @param path Full path.
+ */
+ public void makeDirectories(@NonNull String path) throws IOException {
+ try {
+ int res = mService.makeDirectories(mId, path);
+ if (res < 0) {
+ throw new IOException("makeDirectory() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a file under the current storage directory.
+ *
+ * @param path Relative path of the new file.
+ * @param size Size of the new file in bytes.
+ * @param mode File access permission mode.
+ * @param metadata Metadata bytes.
+ * @param v4signatureBytes Serialized V4SignatureProto.
+ * @param content Optionally set file content.
+ */
+ public void makeFile(@NonNull String path, long size, int mode, @Nullable UUID id,
+ @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes, @Nullable byte[] content)
+ throws IOException {
+ try {
+ if (id == null && metadata == null) {
+ throw new IOException("File ID and metadata cannot both be null");
+ }
+ validateV4Signature(v4signatureBytes);
+ final IncrementalNewFileParams params = new IncrementalNewFileParams();
+ params.size = size;
+ params.metadata = (metadata == null ? new byte[0] : metadata);
+ params.fileId = idToBytes(id);
+ params.signature = v4signatureBytes;
+ int res = mService.makeFile(mId, path, mode, params, content);
+ if (res != 0) {
+ throw new IOException("makeFile() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a file in Incremental storage. The content of the file is mapped from a range inside
+ * a source file in the same storage.
+ *
+ * @param destPath Target full path.
+ * @param sourcePath Source full path.
+ * @param rangeStart Starting offset (in bytes) in the source file.
+ * @param rangeEnd Ending offset (in bytes) in the source file.
+ */
+ public void makeFileFromRange(@NonNull String destPath,
+ @NonNull String sourcePath, long rangeStart, long rangeEnd) throws IOException {
+ try {
+ int res = mService.makeFileFromRange(mId, destPath, sourcePath,
+ rangeStart, rangeEnd);
+ if (res < 0) {
+ throw new IOException("makeFileFromRange() failed, errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a hard-link between two paths, which can be under different storages but in the same
+ * Incremental File System.
+ *
+ * @param sourcePath The absolute path of the source.
+ * @param destStorage The target storage of the link target.
+ * @param destPath The absolute path of the target.
+ */
+ public void makeLink(@NonNull String sourcePath, IncrementalStorage destStorage,
+ @NonNull String destPath) throws IOException {
+ try {
+ int res = mService.makeLink(mId, sourcePath, destStorage.getId(),
+ destPath);
+ if (res < 0) {
+ throw new IOException("makeLink() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deletes a hard-link under the current storage directory.
+ *
+ * @param path The absolute path of the target.
+ */
+ public void unlink(@NonNull String path) throws IOException {
+ try {
+ int res = mService.unlink(mId, path);
+ if (res < 0) {
+ throw new IOException("unlink() failed with errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Rename an old file name to a new file name under the current storage directory.
+ *
+ * @param sourcepath Old file path as a full path to the storage directory.
+ * @param destpath New file path as a full path to the storage directory.
+ */
+ public void moveFile(@NonNull String sourcepath,
+ @NonNull String destpath) throws IOException {
+ //TODO(zyy): implement using rename(2) when confirmed that IncFS supports it.
+ try {
+ int res = mService.makeLink(mId, sourcepath, mId, destpath);
+ if (res < 0) {
+ throw new IOException("moveFile() failed at makeLink(), errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ try {
+ mService.unlink(mId, sourcepath);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Move a directory, which is bind-mounted to a given storage, to a new location. The bind mount
+ * will be persistent between reboots.
+ *
+ * @param sourcePath The old path of the directory as an absolute path.
+ * @param destPath The new path of the directory as an absolute path, expected to already
+ * exist.
+ */
+ public void moveDir(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
+ if (!new File(destPath).exists()) {
+ throw new IOException("moveDir() requires that destination dir already exists.");
+ }
+ try {
+ int res = mService.makeBindMount(mId, sourcePath, destPath,
+ IIncrementalService.BIND_PERMANENT);
+ if (res < 0) {
+ throw new IOException("moveDir() failed at making bind mount, errno " + -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ try {
+ mService.deleteBindMount(mId, sourcePath);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Checks whether a file under the current storage directory is fully loaded.
+ *
+ * @param path The relative path of the file.
+ * @return True if the file is fully loaded.
+ */
+ public boolean isFileFullyLoaded(@NonNull String path) throws IOException {
+ try {
+ int res = mService.isFileFullyLoaded(mId, path);
+ if (res < 0) {
+ throw new IOException("isFileFullyLoaded() failed, errno " + -res);
+ }
+ return res == 0;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+
+ /**
+ * Checks if all files in the storage are fully loaded.
+ */
+ public boolean isFullyLoaded() throws IOException {
+ try {
+ final int res = mService.isFullyLoaded(mId);
+ if (res < 0) {
+ throw new IOException(
+ "isFullyLoaded() failed at querying loading progress, errno " + -res);
+ }
+ return res == 0;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Returns the loading progress of a storage
+ *
+ * @return progress value between [0, 1].
+ */
+ public float getLoadingProgress() throws IOException {
+ try {
+ final float res = mService.getLoadingProgress(mId);
+ if (res < 0) {
+ throw new IOException(
+ "getLoadingProgress() failed at querying loading progress, errno " + -res);
+ }
+ return res;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the metadata object of an IncFs File.
+ *
+ * @param path The relative path of the file.
+ * @return Byte array that contains metadata bytes.
+ */
+ @Nullable
+ public byte[] getFileMetadata(@NonNull String path) {
+ try {
+ return mService.getMetadataByPath(mId, path);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Returns the metadata object of an IncFs File.
+ *
+ * @param id The file id.
+ * @return Byte array that contains metadata bytes.
+ */
+ @Nullable
+ public byte[] getFileMetadata(@NonNull UUID id) {
+ try {
+ final byte[] rawId = idToBytes(id);
+ return mService.getMetadataById(mId, rawId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Initializes and starts the DataLoader.
+ * This makes sure all install-time parameters are applied.
+ * Does not affect persistent DataLoader params.
+ * @return True if start request was successfully queued.
+ */
+ public boolean startLoading(
+ @NonNull DataLoaderParams dataLoaderParams,
+ @Nullable IDataLoaderStatusListener statusListener,
+ @Nullable StorageHealthCheckParams healthCheckParams,
+ @Nullable IStorageHealthListener healthListener,
+ @NonNull PerUidReadTimeouts[] perUidReadTimeouts) {
+ Objects.requireNonNull(perUidReadTimeouts);
+ try {
+ return mService.startLoading(mId, dataLoaderParams.getData(), statusListener,
+ healthCheckParams, healthListener, perUidReadTimeouts);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Marks the completion of installation.
+ */
+ public void onInstallationComplete() {
+ try {
+ mService.onInstallationComplete(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+
+ private static final int UUID_BYTE_SIZE = 16;
+
+ /**
+ * Converts UUID to a byte array usable for Incremental API calls
+ *
+ * @param id The id to convert
+ * @return Byte array that contains the same ID.
+ */
+ @NonNull
+ public static byte[] idToBytes(@Nullable UUID id) {
+ if (id == null) {
+ return new byte[0];
+ }
+ final ByteBuffer buf = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
+ buf.putLong(id.getMostSignificantBits());
+ buf.putLong(id.getLeastSignificantBits());
+ return buf.array();
+ }
+
+ /**
+ * Converts UUID from a byte array usable for Incremental API calls
+ *
+ * @param bytes The id in byte array format, 16 bytes long
+ * @return UUID constructed from the byte array.
+ */
+ @NonNull
+ public static UUID bytesToId(byte[] bytes) throws IllegalArgumentException {
+ if (bytes.length != UUID_BYTE_SIZE) {
+ throw new IllegalArgumentException("Expected array of size " + UUID_BYTE_SIZE
+ + ", got " + bytes.length);
+ }
+ final ByteBuffer buf = ByteBuffer.wrap(bytes);
+ long msb = buf.getLong();
+ long lsb = buf.getLong();
+ return new UUID(msb, lsb);
+ }
+
+ private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
+ private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
+
+ /**
+ * Permanently disable readlogs collection.
+ */
+ public void disallowReadLogs() {
+ try {
+ mService.disallowReadLogs(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deserialize and validate v4 signature bytes.
+ */
+ private static void validateV4Signature(@Nullable byte[] v4signatureBytes)
+ throws IOException {
+ if (v4signatureBytes == null || v4signatureBytes.length == 0) {
+ return;
+ }
+
+ final V4Signature signature;
+ try {
+ signature = V4Signature.readFrom(v4signatureBytes);
+ } catch (IOException e) {
+ throw new IOException("Failed to read v4 signature:", e);
+ }
+
+ if (!signature.isVersionSupported()) {
+ throw new IOException("v4 signature version " + signature.version
+ + " is not supported");
+ }
+
+ final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
+ signature.hashingInfo);
+ final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
+ signature.signingInfos);
+
+ if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
+ throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
+ }
+ if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) {
+ throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize);
+ }
+ if (hashingInfo.salt != null && hashingInfo.salt.length > 0) {
+ throw new IOException("Unsupported salt: " + Arrays.toString(hashingInfo.salt));
+ }
+ if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
+ throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
+ }
+ if (signingInfos.signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
+ throw new IOException(
+ "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
+ }
+ }
+
+ /**
+ * Configure all the lib files inside Incremental Service, e.g., create lib dirs, create new lib
+ * files, extract original lib file data from zip and then write data to the lib files on the
+ * Incremental File System.
+ *
+ * @param apkFullPath Source APK to extract native libs from.
+ * @param libDirRelativePath Target dir to put lib files, e.g., "lib" or "lib/arm".
+ * @param abi Target ABI of the native lib files. Only extract native libs of this ABI.
+ * @param extractNativeLibs If true, extract native libraries; otherwise just setup directories
+ * without extracting.
+ * @return Success of not.
+ */
+ public boolean configureNativeBinaries(String apkFullPath, String libDirRelativePath,
+ String abi, boolean extractNativeLibs) {
+ try {
+ return mService.configureNativeBinaries(mId, apkFullPath, libDirRelativePath, abi,
+ extractNativeLibs);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Waits for all native binary extraction operations to complete on the storage.
+ *
+ * @return Success of not.
+ */
+ public boolean waitForNativeBinariesExtraction() {
+ try {
+ return mService.waitForNativeBinariesExtraction(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Register to listen to loading progress of all the files on this storage.
+ * @param listener To report progress from Incremental Service to the caller.
+ */
+ public boolean registerLoadingProgressListener(IStorageLoadingProgressListener listener) {
+ try {
+ return mService.registerLoadingProgressListener(mId, listener);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Unregister to stop listening to storage loading progress.
+ */
+ public boolean unregisterLoadingProgressListener() {
+ try {
+ return mService.unregisterLoadingProgressListener(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Returns the metrics of the current storage.
+ * {@see IIncrementalService} for metrics keys.
+ */
+ public PersistableBundle getMetrics() {
+ try {
+ return mService.getMetrics(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+}
diff --git a/android-34/android/os/incremental/V4Signature.java b/android-34/android/os/incremental/V4Signature.java
new file mode 100644
index 0000000..2044502
--- /dev/null
+++ b/android-34/android/os/incremental/V4Signature.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+/**
+ * V4 signature fields.
+ * Keep in sync with APKSig authoritative copy.
+ * @hide
+ */
+public class V4Signature {
+ public static final String EXT = ".idsig";
+ public static final int SUPPORTED_VERSION = 2;
+
+ public static final int HASHING_ALGORITHM_SHA256 = 1;
+ public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
+
+ public static final int INCFS_MAX_SIGNATURE_SIZE = 8096; // incrementalfs.h
+
+ /**
+ * IncFS hashing data.
+ */
+ public static class HashingInfo {
+ public final int hashAlgorithm; // only 1 == SHA256 supported
+ public final byte log2BlockSize; // only 12 (block size 4096) supported now
+ @Nullable public final byte[] salt; // used exactly as in fs-verity, 32 bytes max
+ @Nullable public final byte[] rawRootHash; // salted digest of the first Merkle tree page
+
+ HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) {
+ this.hashAlgorithm = hashAlgorithm;
+ this.log2BlockSize = log2BlockSize;
+ this.salt = salt;
+ this.rawRootHash = rawRootHash;
+ }
+
+ /**
+ * Constructs HashingInfo from byte array.
+ */
+ @NonNull
+ public static HashingInfo fromByteArray(@NonNull byte[] bytes) throws IOException {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+ final int hashAlgorithm = buffer.getInt();
+ final byte log2BlockSize = buffer.get();
+ byte[] salt = readBytes(buffer);
+ byte[] rawRootHash = readBytes(buffer);
+ return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash);
+ }
+ }
+
+ /**
+ * Signature data.
+ */
+ public static class SigningInfo {
+ public final byte[] apkDigest; // used to match with the corresponding APK
+ public final byte[] certificate; // ASN.1 DER form
+ public final byte[] additionalData; // a free-form binary data blob
+ public final byte[] publicKey; // ASN.1 DER, must match the certificate
+ public final int signatureAlgorithmId; // see the APK v2 doc for the list
+ public final byte[] signature;
+
+ SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData,
+ byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
+ this.apkDigest = apkDigest;
+ this.certificate = certificate;
+ this.additionalData = additionalData;
+ this.publicKey = publicKey;
+ this.signatureAlgorithmId = signatureAlgorithmId;
+ this.signature = signature;
+ }
+
+ /**
+ * Constructs SigningInfo from byte array.
+ */
+ public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
+ return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+ }
+
+ /**
+ * Constructs SigningInfo from byte buffer.
+ */
+ public static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException {
+ byte[] apkDigest = readBytes(buffer);
+ byte[] certificate = readBytes(buffer);
+ byte[] additionalData = readBytes(buffer);
+ byte[] publicKey = readBytes(buffer);
+ int signatureAlgorithmId = buffer.getInt();
+ byte[] signature = readBytes(buffer);
+ return new SigningInfo(apkDigest, certificate, additionalData, publicKey,
+ signatureAlgorithmId, signature);
+ }
+ }
+
+ /**
+ * Optional signature data block with ID.
+ */
+ public static class SigningInfoBlock {
+ public final int blockId;
+ public final byte[] signingInfo;
+
+ public SigningInfoBlock(int blockId, byte[] signingInfo) {
+ this.blockId = blockId;
+ this.signingInfo = signingInfo;
+ }
+
+ static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException {
+ int blockId = buffer.getInt();
+ byte[] signingInfo = readBytes(buffer);
+ return new SigningInfoBlock(blockId, signingInfo);
+ }
+ }
+
+ /**
+ * V4 signature data.
+ */
+ public static class SigningInfos {
+ // Default signature.
+ public final SigningInfo signingInfo;
+ // Additional signatures corresponding to extended V2/V3/V31 blocks.
+ public final SigningInfoBlock[] signingInfoBlocks;
+
+ public SigningInfos(SigningInfo signingInfo) {
+ this.signingInfo = signingInfo;
+ this.signingInfoBlocks = new SigningInfoBlock[0];
+ }
+
+ public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) {
+ this.signingInfo = signingInfo;
+ this.signingInfoBlocks = signingInfoBlocks;
+ }
+
+ /**
+ * Constructs SigningInfos from byte array.
+ */
+ public static SigningInfos fromByteArray(byte[] bytes) throws IOException {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+ SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer);
+ if (!buffer.hasRemaining()) {
+ return new SigningInfos(signingInfo);
+ }
+ ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1);
+ while (buffer.hasRemaining()) {
+ signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer));
+ }
+ return new SigningInfos(signingInfo,
+ signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()]));
+ }
+ }
+
+ public final int version; // Always 2 for now.
+ /**
+ * Raw byte array containing the IncFS hashing data.
+ * @see HashingInfo#fromByteArray(byte[])
+ */
+ @Nullable public final byte[] hashingInfo;
+
+ /**
+ * Raw byte array containing V4 signatures.
+ * <p>Passed as-is to the kernel. Can be retrieved later.
+ * @see SigningInfos#fromByteArray(byte[])
+ */
+ @Nullable public final byte[] signingInfos;
+
+ /**
+ * Construct a V4Signature from .idsig file.
+ */
+ public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
+ try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) {
+ return readFrom(stream);
+ }
+ }
+
+ /**
+ * Construct a V4Signature from a byte array.
+ */
+ @NonNull
+ public static V4Signature readFrom(@NonNull byte[] bytes) throws IOException {
+ try (InputStream stream = new ByteArrayInputStream(bytes)) {
+ return readFrom(stream);
+ }
+ }
+
+ /**
+ * Store the V4Signature to a byte-array.
+ */
+ public byte[] toByteArray() {
+ try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+ this.writeTo(stream);
+ return stream.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Combines necessary data to a signed data blob.
+ * The blob can be validated against signingInfo.signature.
+ *
+ * @param fileSize - size of the signed file (APK)
+ */
+ public static byte[] getSignedData(long fileSize, HashingInfo hashingInfo,
+ SigningInfo signingInfo) {
+ final int size =
+ 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize(
+ hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize(
+ signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize(
+ signingInfo.additionalData);
+ ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(size);
+ buffer.putLong(fileSize);
+ buffer.putInt(hashingInfo.hashAlgorithm);
+ buffer.put(hashingInfo.log2BlockSize);
+ writeBytes(buffer, hashingInfo.salt);
+ writeBytes(buffer, hashingInfo.rawRootHash);
+ writeBytes(buffer, signingInfo.apkDigest);
+ writeBytes(buffer, signingInfo.certificate);
+ writeBytes(buffer, signingInfo.additionalData);
+ return buffer.array();
+ }
+
+ public boolean isVersionSupported() {
+ return this.version == SUPPORTED_VERSION;
+ }
+
+ private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfos) {
+ this.version = version;
+ this.hashingInfo = hashingInfo;
+ this.signingInfos = signingInfos;
+ }
+
+ private static V4Signature readFrom(InputStream stream) throws IOException {
+ final int version = readIntLE(stream);
+ int maxSize = INCFS_MAX_SIGNATURE_SIZE;
+ final byte[] hashingInfo = readBytes(stream, maxSize);
+ if (hashingInfo != null) {
+ maxSize -= hashingInfo.length;
+ }
+ final byte[] signingInfo = readBytes(stream, maxSize);
+ return new V4Signature(version, hashingInfo, signingInfo);
+ }
+
+ private void writeTo(OutputStream stream) throws IOException {
+ writeIntLE(stream, this.version);
+ writeBytes(stream, this.hashingInfo);
+ writeBytes(stream, this.signingInfos);
+ }
+
+ // Utility methods.
+ private static int bytesSize(byte[] bytes) {
+ return 4/*length*/ + (bytes == null ? 0 : bytes.length);
+ }
+
+ private static void readFully(InputStream stream, byte[] buffer) throws IOException {
+ int len = buffer.length;
+ int n = 0;
+ while (n < len) {
+ int count = stream.read(buffer, n, len - n);
+ if (count < 0) {
+ throw new EOFException();
+ }
+ n += count;
+ }
+ }
+
+ private static int readIntLE(InputStream stream) throws IOException {
+ final byte[] buffer = new byte[4];
+ readFully(stream, buffer);
+ return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
+ }
+
+ private static void writeIntLE(OutputStream stream, int v) throws IOException {
+ final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(
+ v).array();
+ stream.write(buffer);
+ }
+
+ private static byte[] readBytes(InputStream stream, int maxSize) throws IOException {
+ try {
+ final int size = readIntLE(stream);
+ if (size > maxSize) {
+ throw new IOException(
+ "Signature is too long. Max allowed is " + INCFS_MAX_SIGNATURE_SIZE);
+ }
+ final byte[] bytes = new byte[size];
+ readFully(stream, bytes);
+ return bytes;
+ } catch (EOFException ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] readBytes(ByteBuffer buffer) throws IOException {
+ if (buffer.remaining() < 4) {
+ throw new EOFException();
+ }
+ final int size = buffer.getInt();
+ if (buffer.remaining() < size) {
+ throw new EOFException();
+ }
+ final byte[] bytes = new byte[size];
+ buffer.get(bytes);
+ return bytes;
+ }
+
+ private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException {
+ if (bytes == null) {
+ writeIntLE(stream, 0);
+ return;
+ }
+ writeIntLE(stream, bytes.length);
+ stream.write(bytes);
+ }
+
+ private static void writeBytes(ByteBuffer buffer, byte[] bytes) {
+ if (bytes == null) {
+ buffer.putInt(0);
+ return;
+ }
+ buffer.putInt(bytes.length);
+ buffer.put(bytes);
+ }
+}
diff --git a/android-34/android/os/storage/CrateInfo.java b/android-34/android/os/storage/CrateInfo.java
new file mode 100644
index 0000000..418d39e
--- /dev/null
+++ b/android-34/android/os/storage/CrateInfo.java
@@ -0,0 +1,282 @@
+/*
+ * 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.storage;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.UUID;
+
+/**
+ * The CrateInfo describe the crate information.
+ * <p>
+ * It describe the following items.
+ * <ul>
+ * <li>The crate id that is the name of the child directory in
+ * {@link Context#getCrateDir(String)}</li>
+ * <li>Label to provide human readable text for the users.</li>
+ * <li>Expiration information. When the crate is expired and the run .</li>
+ *
+ * </ul>for the directory
+ * </p>
+ * @hide
+ */
+@TestApi
+public final class CrateInfo implements Parcelable {
+ private static final String TAG = "CrateInfo";
+
+ /**
+ * The following member fields whose value are set by apps and retrieved by system_server.
+ */
+ private CharSequence mLabel;
+ @CurrentTimeMillisLong
+ private long mExpiration;
+
+ /**
+ * The following member fields whose value are retrieved by installd.
+ * <p>{@link android.app.usage.StorageStatsManager#queryCratesForUser(UUID, UserHandle)} query
+ * all of crates for the specified UserHandle. That means the return crate list whose elements
+ * may have the same userId but different package name. Each crate needs the information to tell
+ * the caller from where package comes.
+ * </p>
+ */
+ private int mUid;
+
+ /**
+ * The following member fields whose value are retrieved by installd.
+ * <p>Both {@link StorageStatsManager#queryCratesForUid(UUID, int)} and
+ * {@link android.app.usage.StorageStatsManager#queryCratesForUser(UUID, UserHandle)} query
+ * all of crates for the specified uid or userId. That means the return crate list whose
+ * elements may have the same uid or userId but different package name. Each crate needs the
+ * information to tell the caller from where package comes.
+ * </p>
+ */
+ @Nullable
+ private String mPackageName;
+
+ /**
+ * The following member fields whose value are retrieved by system_server.
+ * <p>
+ * The child directories in {@link Context#getCrateDir(String)} are crates. Each directories
+ * is a crate. The folder name is the crate id.
+ * </p><p>
+ * Can't apply check if the path is validated or not because it need pass through the
+ * parcel.
+ * </p>
+ */
+ @Nullable
+ private String mId;
+
+ private CrateInfo() {
+ mExpiration = 0;
+ }
+
+ /**
+ * To create the crateInfo by passing validated label.
+ * @param label a display name for the crate
+ * @param expiration It's positive integer. if current time is larger than the expiration, the
+ * files under this crate will be considered to be deleted. Default value is 0.
+ * @throws IllegalArgumentException cause IllegalArgumentException when label is null
+ * or empty string
+ */
+ public CrateInfo(@NonNull CharSequence label, @CurrentTimeMillisLong long expiration) {
+ Preconditions.checkStringNotEmpty(label,
+ "Label should not be either null or empty string");
+ Preconditions.checkArgumentNonnegative(expiration,
+ "Expiration should be non negative number");
+
+ mLabel = label;
+ mExpiration = expiration;
+ }
+
+ /**
+ * To create the crateInfo by passing validated label.
+ * @param label a display name for the crate
+ * @throws IllegalArgumentException cause IllegalArgumentException when label is null
+ * or empty string
+ */
+ public CrateInfo(@NonNull CharSequence label) {
+ this(label, 0);
+ }
+
+ /**
+ * To get the meaningful text of the crate for the users.
+ * @return the meaningful text
+ */
+ @NonNull
+ public CharSequence getLabel() {
+ if (TextUtils.isEmpty(mLabel)) {
+ return mId;
+ }
+ return mLabel;
+ }
+
+
+ /**
+ * To return the expiration time.
+ * <p>
+ * If the current time is larger than expiration time, the crate files are considered to be
+ * deleted.
+ * </p>
+ * @return the expiration time
+ */
+ @CurrentTimeMillisLong
+ public long getExpirationMillis() {
+ return mExpiration;
+ }
+
+ /**
+ * To set the expiration time.
+ * @param expiration the expiration time
+ * @hide
+ */
+ public void setExpiration(@CurrentTimeMillisLong long expiration) {
+ Preconditions.checkArgumentNonnegative(expiration);
+ mExpiration = expiration;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ /**
+ * To compare with crateinfo when selves' mId is validated.
+ * <p>The validated crateinfo.mId must be validated the following items.
+ * <ul>
+ * <li>mId is not null</li>
+ * <li>mId is not empty string</li>
+ * </ul>
+ * </p>
+ * @param obj the reference object with which to compare.
+ * @return true when selves's mId is validated and equal to crateinfo.mId.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof CrateInfo) {
+ CrateInfo crateInfo = (CrateInfo) obj;
+ if (!TextUtils.isEmpty(mId)
+ && TextUtils.equals(mId, crateInfo.mId)) {
+ return true;
+ }
+ }
+
+ return super.equals(obj);
+ }
+
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ if (dest == null) {
+ return;
+ }
+
+ dest.writeCharSequence(mLabel);
+ dest.writeLong(mExpiration);
+
+ dest.writeInt(mUid);
+ dest.writeString(mPackageName);
+ dest.writeString(mId);
+ }
+
+ /**
+ * To read the data from parcel.
+ * <p>
+ * It's called by StorageStatsService.
+ * </p>
+ * @hide
+ */
+ public void readFromParcel(@Nullable Parcel in) {
+ if (in == null) {
+ return;
+ }
+
+ mLabel = in.readCharSequence();
+ mExpiration = in.readLong();
+
+ mUid = in.readInt();
+ mPackageName = in.readString();
+ mId = in.readString();
+ }
+
+ @NonNull
+ public static final Creator<CrateInfo> CREATOR = new Creator<CrateInfo>() {
+ @NonNull
+ @Override
+ public CrateInfo createFromParcel(@NonNull Parcel in) {
+ CrateInfo crateInfo = new CrateInfo();
+ crateInfo.readFromParcel(in);
+ return crateInfo;
+ }
+
+ @NonNull
+ @Override
+ public CrateInfo[] newArray(int size) {
+ return new CrateInfo[size];
+ }
+ };
+
+ /**
+ * To copy the information from service into crateinfo.
+ * <p>
+ * This function is called in system_server. The copied information includes
+ * <ul>
+ * <li>uid</li>
+ * <li>package name</li>
+ * <li>crate id</li>
+ * </ul>
+ * </p>
+ * @param uid the uid that the crate belong to
+ * @param packageName the package name that the crate belong to
+ * @param id the crate dir
+ * @return the CrateInfo instance
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public static CrateInfo copyFrom(int uid, @Nullable String packageName, @Nullable String id) {
+ if (!UserHandle.isApp(uid) || TextUtils.isEmpty(packageName) || TextUtils.isEmpty(id)) {
+ return null;
+ }
+
+ CrateInfo crateInfo = new CrateInfo(id /* default label = id */, 0);
+ crateInfo.mUid = uid;
+ crateInfo.mPackageName = packageName;
+ crateInfo.mId = id;
+ return crateInfo;
+ }
+}
diff --git a/android-34/android/os/storage/DiskInfo.java b/android-34/android/os/storage/DiskInfo.java
new file mode 100644
index 0000000..d32928c
--- /dev/null
+++ b/android-34/android/os/storage/DiskInfo.java
@@ -0,0 +1,233 @@
+/*
+ * 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.compat.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;
+ /** The FLAG_STUB_VISIBLE is set from vold, which gets the flag from outside (e.g., ChromeOS) */
+ public static final int FLAG_STUB_VISIBLE = 1 << 6;
+
+ 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;
+ }
+
+ public boolean isStubVisible() {
+ return (flags & FLAG_STUB_VISIBLE) != 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(@Nullable 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-34/android/os/storage/OnObbStateChangeListener.java b/android-34/android/os/storage/OnObbStateChangeListener.java
new file mode 100644
index 0000000..1fb1782
--- /dev/null
+++ b/android-34/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-34/android/os/storage/StorageEventListener.java b/android-34/android/os/storage/StorageEventListener.java
new file mode 100644
index 0000000..694ff19
--- /dev/null
+++ b/android-34/android/os/storage/StorageEventListener.java
@@ -0,0 +1,70 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+/**
+ * Used for receiving notifications from the StorageManager
+ *
+ * @hide
+ */
+public class StorageEventListener {
+
+ @UnsupportedAppUsage
+ public 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void onVolumeRecordChanged(VolumeRecord rec) {
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void onVolumeForgotten(String fsUuid) {
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void onDiskScanned(DiskInfo disk, int volumeCount) {
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void onDiskDestroyed(DiskInfo disk) {
+ }
+}
diff --git a/android-34/android/os/storage/StorageManager.java b/android-34/android/os/storage/StorageManager.java
new file mode 100644
index 0000000..80dd488
--- /dev/null
+++ b/android-34/android/os/storage/StorageManager.java
@@ -0,0 +1,2911 @@
+/*
+ * 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.MANAGE_EXTERNAL_STORAGE;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
+import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES;
+import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.PER_USER_RANGE;
+
+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.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.WorkerThread;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
+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.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+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.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.MediaStore;
+import android.provider.Settings;
+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.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.Locale;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+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_SDCARDFS = "persist.sys.sdcardfs";
+ /** {@hide} */
+ public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
+ /** {@hide} */
+ public static final String PROP_FORCED_SCOPED_STORAGE_WHITELIST =
+ "forced_scoped_storage_whitelist";
+
+ /** {@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: See comments around #convert for more details.
+ private static final String FAT_UUID_PREFIX = "fafafafa-fafa-5afa-8afa-fafa";
+
+ // 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";
+
+ /**
+ * Activity Action: Allows the user to free up space by clearing app external cache directories.
+ * The intent doesn't automatically clear cache, but shows a dialog and lets the user decide.
+ * <p>
+ * This intent should be launched using
+ * {@link Activity#startActivityForResult(Intent, int)} so that the user
+ * knows which app is requesting to clear cache. The returned result will be:
+ * {@link Activity#RESULT_OK} if the activity was launched and all cache was cleared,
+ * {@link OsConstants#EIO} if an error occurred while clearing the cache or
+ * {@link Activity#RESULT_CANCELED} otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE";
+
+ /**
+ * 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_SDCARDFS_FORCE_ON = 1 << 2;
+ /** {@hide} */
+ public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 3;
+ /** {@hide} */
+ public static final int DEBUG_VIRTUAL_DISK = 1 << 4;
+
+ /** {@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_STORAGE_SDK = IInstalld.FLAG_STORAGE_SDK;
+
+ /** {@hide} */
+ @IntDef(prefix = "FLAG_STORAGE_", value = {
+ FLAG_STORAGE_DE,
+ FLAG_STORAGE_CE,
+ FLAG_STORAGE_EXTERNAL,
+ FLAG_STORAGE_SDK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StorageFlags {}
+
+ /** {@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 FLAG_INCLUDE_RECENT = 1 << 11;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_SHARED_PROFILE = 1 << 12;
+
+ /** {@hide} */
+ public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
+
+ /** @hide The volume is not encrypted. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int ENCRYPTION_STATE_NONE = 1;
+
+ 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);
+
+ @GuardedBy("mDelegates")
+ private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
+
+ private class StorageEventListenerDelegate extends IStorageEventListener.Stub {
+ final Executor mExecutor;
+ final StorageEventListener mListener;
+ final StorageVolumeCallback mCallback;
+
+ public StorageEventListenerDelegate(@NonNull Executor executor,
+ @NonNull StorageEventListener listener, @NonNull StorageVolumeCallback callback) {
+ mExecutor = executor;
+ mListener = listener;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
+ mExecutor.execute(() -> {
+ mListener.onUsbMassStorageConnectionChanged(connected);
+ });
+ }
+
+ @Override
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ mExecutor.execute(() -> {
+ mListener.onStorageStateChanged(path, oldState, newState);
+
+ if (path != null) {
+ for (StorageVolume sv : getStorageVolumes()) {
+ if (Objects.equals(path, sv.getPath())) {
+ mCallback.onStateChanged(sv);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ mExecutor.execute(() -> {
+ mListener.onVolumeStateChanged(vol, oldState, newState);
+
+ final File path = vol.getPathForUser(UserHandle.myUserId());
+ if (path != null) {
+ for (StorageVolume sv : getStorageVolumes()) {
+ if (Objects.equals(path.getAbsolutePath(), sv.getPath())) {
+ mCallback.onStateChanged(sv);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onVolumeRecordChanged(VolumeRecord rec) {
+ mExecutor.execute(() -> {
+ mListener.onVolumeRecordChanged(rec);
+ });
+ }
+
+ @Override
+ public void onVolumeForgotten(String fsUuid) {
+ mExecutor.execute(() -> {
+ mListener.onVolumeForgotten(fsUuid);
+ });
+ }
+
+ @Override
+ public void onDiskScanned(DiskInfo disk, int volumeCount) {
+ mExecutor.execute(() -> {
+ mListener.onDiskScanned(disk, volumeCount);
+ });
+ }
+
+ @Override
+ public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
+ mExecutor.execute(() -> {
+ mListener.onDiskDestroyed(disk);
+ });
+ }
+ }
+
+ /**
+ * 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(
+ mContext.getMainExecutor(), listener, new StorageVolumeCallback());
+ 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.mListener == listener) {
+ try {
+ mStorageManager.unregisterListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ i.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback that delivers {@link StorageVolume} related events.
+ * <p>
+ * For example, this can be used to detect when a volume changes to the
+ * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+ * states.
+ *
+ * @see StorageManager#registerStorageVolumeCallback
+ * @see StorageManager#unregisterStorageVolumeCallback
+ */
+ public static class StorageVolumeCallback {
+ /**
+ * Called when {@link StorageVolume#getState()} changes, such as
+ * changing to the {@link Environment#MEDIA_MOUNTED} or
+ * {@link Environment#MEDIA_UNMOUNTED} states.
+ * <p>
+ * The given argument is a snapshot in time and can be used to process
+ * events in the order they occurred, or you can call
+ * {@link StorageManager#getStorageVolumes()} to observe the latest
+ * value.
+ */
+ public void onStateChanged(@NonNull StorageVolume volume) { }
+ }
+
+ /**
+ * Registers the given callback to listen for {@link StorageVolume} changes.
+ * <p>
+ * For example, this can be used to detect when a volume changes to the
+ * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+ * states.
+ *
+ * @see StorageManager#unregisterStorageVolumeCallback
+ */
+ public void registerStorageVolumeCallback(@CallbackExecutor @NonNull Executor executor,
+ @NonNull StorageVolumeCallback callback) {
+ synchronized (mDelegates) {
+ final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+ executor, new StorageEventListener(), callback);
+ try {
+ mStorageManager.registerListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mDelegates.add(delegate);
+ }
+ }
+
+ /**
+ * Unregisters the given callback from listening for {@link StorageVolume}
+ * changes.
+ *
+ * @see StorageManager#registerStorageVolumeCallback
+ */
+ public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) {
+ synchronized (mDelegates) {
+ for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ final StorageEventListenerDelegate delegate = i.next();
+ if (delegate.mCallback == callback) {
+ try {
+ mStorageManager.unregisterListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ i.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Enables USB Mass Storage (UMS) on the device.
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void enableUsbMassStorage() {
+ }
+
+ /**
+ * Disables USB Mass Storage (UMS) on the device.
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void disableUsbMassStorage() {
+ }
+
+ /**
+ * Query if a USB Mass Storage (UMS) host is connected.
+ * @return true if UMS host is connected.
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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.
+ * <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 must be <code>null</code>. Previously, some Android device
+ * implementations accepted a non-<code>null</code> key to mount
+ * an encrypted OBB file. However, this never worked reliably and
+ * is no longer supported.
+ * @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.checkArgument(key == null, "mounting encrypted OBBs is no longer supported");
+ 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, mObbActionListener, nonce,
+ getObbInfo(canonicalPath));
+ return true;
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a {@link PendingIntent} that can be used by Apps with
+ * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission
+ * to launch the manageSpaceActivity for any App that implements it, irrespective of its
+ * exported status.
+ * <p>
+ * Caller has the responsibility of supplying a valid packageName which has
+ * manageSpaceActivity implemented.
+ *
+ * @param packageName package name for the App for which manageSpaceActivity is to be launched
+ * @param requestCode for launching the activity
+ * @return PendingIntent to launch the manageSpaceActivity if successful, null if the
+ * packageName doesn't have a manageSpaceActivity.
+ * @throws IllegalArgumentException an invalid packageName is supplied.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ @Nullable
+ public PendingIntent getManageSpaceActivityIntent(
+ @NonNull String packageName, int requestCode) {
+ try {
+ return mStorageManager.getManageSpaceActivityIntent(packageName,
+ requestCode);
+ } 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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) {
+ String id = emulatedVol.getId();
+ int idx = id.indexOf(";");
+ if (idx != -1) {
+ id = id.substring(0, idx);
+ }
+ return findVolumeById(id.replace("emulated", "private"));
+ } else {
+ return null;
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
+ if (privateVol != null) {
+ return findVolumeById(privateVol.getId().replace("private", "emulated") + ";"
+ + mContext.getUserId());
+ } 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(@NonNull File file) {
+ return getStorageVolume(getVolumeList(), file);
+ }
+
+ /**
+ * Return the {@link StorageVolume} that contains the given
+ * {@link MediaStore} item.
+ */
+ public @NonNull StorageVolume getStorageVolume(@NonNull Uri uri) {
+ String volumeName = MediaStore.getVolumeName(uri);
+
+ // When Uri is pointing at a synthetic volume, we're willing to query to
+ // resolve the actual volume name
+ if (Objects.equals(volumeName, MediaStore.VOLUME_EXTERNAL)) {
+ try (Cursor c = mContext.getContentResolver().query(uri,
+ new String[] { MediaStore.MediaColumns.VOLUME_NAME }, null, null)) {
+ if (c.moveToFirst()) {
+ volumeName = c.getString(0);
+ }
+ }
+ }
+
+ switch (volumeName) {
+ case MediaStore.VOLUME_EXTERNAL_PRIMARY:
+ return getPrimaryStorageVolume();
+ default:
+ for (StorageVolume vol : getStorageVolumes()) {
+ if (Objects.equals(vol.getMediaStoreVolumeName(), 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 currently available to
+ * the calling user.
+ * <p>
+ * These storage volumes are actively attached to the device, but may be in
+ * any mount state, as returned by {@link StorageVolume#getState()}. Returns
+ * both the primary shared storage device and any attached external volumes,
+ * including SD cards and USB drives.
+ */
+ 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 list of shared/external storage volumes currently available to
+ * the calling user and the user it shares media with. Please refer to
+ * <a href="https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support">
+ * multi-user support</a> for more details.
+ *
+ * <p>
+ * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also
+ * includes the volumes belonging to any user it shares media with
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ public @NonNull List<StorageVolume> getStorageVolumesIncludingSharedProfiles() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE));
+ return res;
+ }
+
+ /**
+ * Return the list of shared/external storage volumes both currently and
+ * recently available to the calling user.
+ * <p>
+ * Recently available storage volumes are likely to reappear in the future,
+ * so apps are encouraged to preserve any indexed metadata related to these
+ * volumes to optimize user experiences.
+ */
+ public @NonNull List<StorageVolume> getRecentStorageVolumes() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_RECENT));
+ 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) {
+ Log.w(TAG, "Missing package names; no storage volumes available");
+ return new StorageVolume[0];
+ }
+ packageName = packageNames[0];
+ }
+ return storageManager.getVolumeList(userId, 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");
+ }
+
+ /**
+ * Devices having above STORAGE_THRESHOLD_PERCENT_HIGH of total space free are considered to be
+ * in high free space category.
+ *
+ * @hide
+ */
+ public static final int DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH = 20;
+ /** {@hide} */
+ @TestApi
+ public static final String
+ STORAGE_THRESHOLD_PERCENT_HIGH_KEY = "storage_threshold_percent_high";
+ /**
+ * Devices having below STORAGE_THRESHOLD_PERCENT_LOW of total space free are considered to be
+ * in low free space category and can be configured via
+ * Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE.
+ *
+ * @hide
+ */
+ public static final int DEFAULT_STORAGE_THRESHOLD_PERCENT_LOW = 5;
+ /**
+ * For devices in high free space category, CACHE_RESERVE_PERCENT_HIGH percent of total space is
+ * allocated for cache.
+ *
+ * @hide
+ */
+ public static final int DEFAULT_CACHE_RESERVE_PERCENT_HIGH = 10;
+ /** {@hide} */
+ @TestApi
+ public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high";
+ /**
+ * For devices in low free space category, CACHE_RESERVE_PERCENT_LOW percent of total space is
+ * allocated for cache.
+ *
+ * @hide
+ */
+ public static final int DEFAULT_CACHE_RESERVE_PERCENT_LOW = 2;
+ /** {@hide} */
+ @TestApi
+ public static final String CACHE_RESERVE_PERCENT_LOW_KEY = "cache_reserve_percent_low";
+
+ private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
+
+ 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_STORAGE_THRESHOLD_PERCENT_LOW);
+ 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);
+ }
+
+ /**
+ * Compute the minimum number of bytes of storage on the device that could
+ * be reserved for cached data depending on the device state which is then passed on
+ * to getStorageCacheBytes.
+ *
+ * Input File path must point to a storage volume.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @SuppressLint("StreamFiles")
+ public long computeStorageCacheBytes(@NonNull File path) {
+ final int storageThresholdPercentHigh = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ STORAGE_THRESHOLD_PERCENT_HIGH_KEY, DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH);
+ final int cacheReservePercentHigh = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ CACHE_RESERVE_PERCENT_HIGH_KEY, DEFAULT_CACHE_RESERVE_PERCENT_HIGH);
+ final int cacheReservePercentLow = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ CACHE_RESERVE_PERCENT_LOW_KEY, DEFAULT_CACHE_RESERVE_PERCENT_LOW);
+ final long totalBytes = path.getTotalSpace();
+ final long usableBytes = path.getUsableSpace();
+ final long storageThresholdHighBytes = totalBytes * storageThresholdPercentHigh / 100;
+ final long storageThresholdLowBytes = getStorageLowBytes(path);
+ long result;
+ if (usableBytes > storageThresholdHighBytes) {
+ // If free space is >storageThresholdPercentHigh of total space,
+ // reserve cacheReservePercentHigh of total space
+ result = totalBytes * cacheReservePercentHigh / 100;
+ } else if (usableBytes < storageThresholdLowBytes) {
+ // If free space is <min(storageThresholdPercentLow of total space, 500MB),
+ // reserve cacheReservePercentLow of total space
+ result = totalBytes * cacheReservePercentLow / 100;
+ } else {
+ // Else, linearly interpolate the amount of space to reserve
+ double slope = (cacheReservePercentHigh - cacheReservePercentLow) * totalBytes
+ / (100.0 * (storageThresholdHighBytes - storageThresholdLowBytes));
+ double intercept = totalBytes * cacheReservePercentLow / 100.0
+ - storageThresholdLowBytes * slope;
+ result = Math.round(slope * usableBytes + intercept);
+ }
+ return result;
+ }
+
+ /**
+ * Return the minimum number of bytes of storage on the device that should
+ * be reserved for cached data.
+ *
+ * @hide
+ */
+ public long getStorageCacheBytes(@NonNull File path, @AllocateFlags int flags) {
+ 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 computeStorageCacheBytes(path) / 2;
+ } else {
+ return computeStorageCacheBytes(path);
+ }
+ }
+
+ /**
+ * Return the number of available bytes at which the given path is
+ * considered full.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 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} */
+ @TestApi
+ 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 encrypted?
+ * <p>
+ * Note: all devices launching with Android 10 (API level 29) or later are
+ * required to be encrypted. This should only ever return false for
+ * in-development devices on which encryption has not yet been configured.
+ *
+ * @return true if encrypted, false if not encrypted
+ */
+ public static boolean isEncrypted() {
+ return RoSystemProperties.CRYPTO_ENCRYPTED;
+ }
+
+ /** {@hide}
+ * Does this device have file-based encryption (FBE) enabled?
+ * @return true if the device has file-based encryption enabled.
+ */
+ public static boolean isFileEncrypted() {
+ if (!isEncrypted()) {
+ return false;
+ }
+ return RoSystemProperties.CRYPTO_FILE_ENCRYPTED;
+ }
+
+ /** {@hide}
+ * @deprecated Use {@link #isFileEncrypted} instead, since emulated FBE is no longer supported.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @Deprecated
+ public static boolean isFileEncryptedNativeOnly() {
+ return isFileEncrypted();
+ }
+
+ /** {@hide}
+ * @deprecated Use {@link #isFileEncrypted} instead, since emulated FBE is no longer supported.
+ */
+ @Deprecated
+ public static boolean isFileEncryptedNativeOrEmulated() {
+ return isFileEncrypted();
+ }
+
+ /** {@hide} */
+ public static boolean hasAdoptable() {
+ switch (SystemProperties.get(PROP_ADOPTABLE)) {
+ case "force_on":
+ return true;
+ case "force_off":
+ return false;
+ default:
+ return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false);
+ }
+ }
+
+ /**
+ * Return if the currently booted device has the "isolated storage" feature
+ * flag enabled.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static boolean hasIsolatedStorage() {
+ return false;
+ }
+
+ /**
+ * @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, @NonNull String featureId, String permission, int op) {
+ return checkPermissionAndAppOp(context, enforce, pid, uid, packageName, featureId,
+ 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,
+ null /* featureId is not needed when not noting */, 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, @Nullable String featureId, 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, featureId, null);
+ } 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,
+ @Nullable String featureId, String permission, int op) {
+ return checkPermissionAndAppOp(mContext, enforce, pid, uid, packageName, featureId,
+ permission, op);
+ }
+
+ private boolean noteAppOpAllowingLegacy(boolean enforce,
+ int pid, int uid, String packageName, @Nullable String featureId, int op) {
+ final int mode = mAppOps.noteOpNoThrow(op, uid, packageName, featureId, null);
+ 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.
+
+ /**
+ * @deprecated This method should not be used since it check slegacy permissions,
+ * no longer valid. Clients should check the appropriate permissions directly
+ * instead (e.g. READ_MEDIA_IMAGES).
+ *
+ * {@hide}
+ */
+ @Deprecated
+ public boolean checkPermissionReadImages(boolean enforce,
+ int pid, int uid, String packageName, @Nullable String featureId) {
+ if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
+ READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) {
+ return false;
+ }
+ return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
+ OP_READ_MEDIA_IMAGES);
+ }
+
+ private boolean checkExternalStoragePermissionAndAppOp(boolean enforce,
+ int pid, int uid, String packageName, @Nullable String featureId, String permission,
+ int op) {
+ // First check if app has MANAGE_EXTERNAL_STORAGE.
+ final int mode = mAppOps.noteOpNoThrow(OP_MANAGE_EXTERNAL_STORAGE, uid, packageName,
+ featureId, null);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+ if (mode == AppOpsManager.MODE_DEFAULT && mContext.checkPermission(
+ MANAGE_EXTERNAL_STORAGE, pid, uid) == PERMISSION_GRANTED) {
+ return true;
+ }
+ // If app doesn't have MANAGE_EXTERNAL_STORAGE, then check if it has requested granular
+ // permission.
+ return checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, permission, op);
+ }
+
+ /** {@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();
+ }
+ }
+
+
+ /** @hide */
+ @IntDef(prefix = { "MOUNT_MODE_" }, value = {
+ MOUNT_MODE_EXTERNAL_NONE,
+ MOUNT_MODE_EXTERNAL_DEFAULT,
+ MOUNT_MODE_EXTERNAL_INSTALLER,
+ MOUNT_MODE_EXTERNAL_PASS_THROUGH,
+ MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE
+ })
+ /** @hide */
+ public @interface MountMode {}
+
+ /**
+ * No external storage should be mounted.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
+ /**
+ * Default external storage should be mounted.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
+ /**
+ * Mount mode for package installers which should give them access to
+ * all obb dirs in addition to their package sandboxes
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
+ /**
+ * The lower file system should be bind mounted directly on external storage
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+
+ /**
+ * Use the regular scoped storage filesystem, but Android/ should be writable.
+ * Used to support the applications hosting DownloadManager and the MTP server.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE =
+ IVold.REMOUNT_MODE_ANDROID_WRITABLE;
+ /**
+ * 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;
+
+ /**
+ * Flag indicating that a disk space check should not take into account
+ * freeable cached space when determining allocatable space.
+ *
+ * Intended for use with {@link #getAllocatableBytes()}.
+ * @hide
+ */
+ public static final int FLAG_ALLOCATE_NON_CACHE_ONLY = 1 << 3;
+
+ /**
+ * Flag indicating that a disk space check should only return freeable
+ * cached space when determining allocatable space.
+ *
+ * Intended for use with {@link #getAllocatableBytes()}.
+ * @hide
+ */
+ public static final int FLAG_ALLOCATE_CACHE_ONLY = 1 << 4;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_ALLOCATE_" }, value = {
+ FLAG_ALLOCATE_AGGRESSIVE,
+ FLAG_ALLOCATE_DEFY_ALL_RESERVED,
+ FLAG_ALLOCATE_DEFY_HALF_RESERVED,
+ FLAG_ALLOCATE_NON_CACHE_ONLY,
+ FLAG_ALLOCATE_CACHE_ONLY,
+ })
+ @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("RequiresPermission")
+ 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("RequiresPermission")
+ 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();
+ }
+ }
+
+ /**
+ * Returns the External Storage mount mode corresponding to the given uid and packageName.
+ * These mount modes specify different views and access levels for
+ * different apps on external storage.
+ *
+ * @params uid UID of the application
+ * @params packageName name of the package
+ * @return {@code MountMode} for the given uid and packageName.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+ @SystemApi
+ @MountMode
+ public int getExternalStorageMountMode(int uid, @NonNull String packageName) {
+ try {
+ return mStorageManager.getExternalStorageMountMode(uid, packageName);
+ } 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("RequiresPermission")
+ 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";
+
+
+ // Project IDs below must match android_projectid_config.h
+ /**
+ * Default project ID for files on external storage
+ *
+ * {@hide}
+ */
+ public static final int PROJECT_ID_EXT_DEFAULT = 1000;
+
+ /**
+ * project ID for audio files on external storage
+ *
+ * {@hide}
+ */
+ public static final int PROJECT_ID_EXT_MEDIA_AUDIO = 1001;
+
+ /**
+ * project ID for video files on external storage
+ *
+ * {@hide}
+ */
+ public static final int PROJECT_ID_EXT_MEDIA_VIDEO = 1002;
+
+ /**
+ * project ID for image files on external storage
+ *
+ * {@hide}
+ */
+ public static final int PROJECT_ID_EXT_MEDIA_IMAGE = 1003;
+
+ /**
+ * Constant for use with
+ * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+ * is not a media file.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int QUOTA_TYPE_MEDIA_NONE = 0;
+
+ /**
+ * Constant for use with
+ * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+ * is an image file.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int QUOTA_TYPE_MEDIA_IMAGE = 1;
+
+ /**
+ * Constant for use with
+ * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+ * is an audio file.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int QUOTA_TYPE_MEDIA_AUDIO = 2;
+
+ /**
+ * Constant for use with
+ * {@link #updateExternalStorageFileQuotaType(String, int)} (String, int)}, to indicate the file
+ * is a video file.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int QUOTA_TYPE_MEDIA_VIDEO = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "QUOTA_TYPE_" }, value = {
+ QUOTA_TYPE_MEDIA_NONE,
+ QUOTA_TYPE_MEDIA_AUDIO,
+ QUOTA_TYPE_MEDIA_VIDEO,
+ QUOTA_TYPE_MEDIA_IMAGE,
+ })
+ public @interface QuotaType {}
+
+ private static native boolean setQuotaProjectId(String path, long projectId);
+
+ private static long getProjectIdForUser(int userId, int projectId) {
+ // Much like UserHandle.getUid(), store the user ID in the upper bits
+ return userId * PER_USER_RANGE + projectId;
+ }
+
+ /**
+ * Let StorageManager know that the quota type for a file on external storage should
+ * be updated. Android tracks quotas for various media types. Consequently, this should be
+ * called on first creation of a new file on external storage, and whenever the
+ * media type of the file is updated later.
+ *
+ * This API doesn't require any special permissions, though typical implementations
+ * will require being called from an SELinux domain that allows setting file attributes
+ * related to quota (eg the GID or project ID).
+ * If the calling user has MANAGE_EXTERNAL_STORAGE permissions, quota for shared profile's
+ * volumes is also updated.
+ *
+ * The default platform user of this API is the MediaProvider process, which is
+ * responsible for managing all of external storage.
+ *
+ * @param path the path to the file for which we should update the quota type
+ * @param quotaType the quota type of the file; this is based on the
+ * {@code QuotaType} constants, eg
+ * {@code StorageManager.QUOTA_TYPE_MEDIA_AUDIO}
+ *
+ * @throws IllegalArgumentException if {@code quotaType} does not correspond to a valid
+ * quota type.
+ * @throws IOException if the quota type could not be updated.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void updateExternalStorageFileQuotaType(@NonNull File path,
+ @QuotaType int quotaType) throws IOException {
+ long projectId;
+ final String filePath = path.getCanonicalPath();
+ int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
+ // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also
+ // returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
+ if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+ volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
+ }
+ final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
+ final StorageVolume volume = getStorageVolume(availableVolumes, path);
+ if (volume == null) {
+ Log.w(TAG, "Failed to update quota type for " + filePath);
+ return;
+ }
+ if (!volume.isEmulated()) {
+ // We only support quota tracking on emulated filesystems
+ return;
+ }
+
+ final int userId = volume.getOwner().getIdentifier();
+ if (userId < 0) {
+ throw new IllegalStateException("Failed to update quota type for " + filePath);
+ }
+ switch (quotaType) {
+ case QUOTA_TYPE_MEDIA_NONE:
+ projectId = getProjectIdForUser(userId, PROJECT_ID_EXT_DEFAULT);
+ break;
+ case QUOTA_TYPE_MEDIA_AUDIO:
+ projectId = getProjectIdForUser(userId, PROJECT_ID_EXT_MEDIA_AUDIO);
+ break;
+ case QUOTA_TYPE_MEDIA_VIDEO:
+ projectId = getProjectIdForUser(userId, PROJECT_ID_EXT_MEDIA_VIDEO);
+ break;
+ case QUOTA_TYPE_MEDIA_IMAGE:
+ projectId = getProjectIdForUser(userId, PROJECT_ID_EXT_MEDIA_IMAGE);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid quota type: " + quotaType);
+ }
+ if (!setQuotaProjectId(filePath, projectId)) {
+ throw new IOException("Failed to update quota type for " + filePath);
+ }
+ }
+
+ /**
+ * Asks StorageManager to fixup the permissions of an application-private directory.
+ *
+ * On devices without sdcardfs, filesystem permissions aren't magically fixed up. This
+ * is problematic mostly in application-private directories, which are owned by the
+ * application itself; if another process with elevated permissions creates a file
+ * in these directories, the UID will be wrong, and the owning package won't be able
+ * to access the files.
+ *
+ * This API can be used to recursively fix up the permissions on the passed in path.
+ * The default platform user of this API is the DownloadProvider, which can download
+ * things in application-private directories on their behalf.
+ *
+ * This API doesn't require any special permissions, because it merely changes the
+ * permissions of a directory to what they should anyway be.
+ *
+ * @param path the path for which we should fix up the permissions
+ *
+ * @hide
+ */
+ public void fixupAppDir(@NonNull File path) {
+ try {
+ mStorageManager.fixupAppDir(path.getCanonicalPath());
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to get canonical path for " + path.getPath(), e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@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);
+ }
+
+ /**
+ * Returns true if {@code uuid} is a FAT volume identifier. FAT Volume identifiers
+ * are 32 randomly generated bits that are represented in string form as AAAA-AAAA.
+ */
+ private static boolean isFatVolumeIdentifier(String uuid) {
+ return uuid.length() == 9 && uuid.charAt(4) == '-';
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static @NonNull UUID convert(@Nullable String uuid) {
+ // UUID_PRIVATE_INTERNAL is null, so this accepts nullable input
+ 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 if (isFatVolumeIdentifier(uuid)) {
+ // FAT volume identifiers are not UUIDs but we need to coerce them into
+ // UUIDs in order to satisfy apis that take java.util.UUID arguments.
+ //
+ // We coerce a 32 bit fat volume identifier of the form XXXX-YYYY into
+ // a UUID of form "fafafafa-fafa-5afa-8afa-fafaXXXXYYYY". This is an
+ // RFC-422 UUID with Version 5, which is a namespaced UUID. The UUIDs we
+ // coerce into are not true namespace UUIDs; although FAT storage volume
+ // identifiers are unique names within a fixed namespace, this UUID is not
+ // based on an SHA-1 hash of the name. We avoid the SHA-1 hash because
+ // (a) we need this transform to be reversible (b) it's pointless to generate
+ // a 128 bit hash from a 32 bit value.
+ return UUID.fromString(FAT_UUID_PREFIX + uuid.replace("-", ""));
+ } else {
+ return UUID.fromString(uuid);
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static @NonNull String convert(@NonNull 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 {
+ String uuidString = storageUuid.toString();
+ // This prefix match will exclude fsUuids from private volumes because
+ // (a) linux fsUuids are generally Version 4 (random) UUIDs so the prefix
+ // will contain 4xxx instead of 5xxx and (b) we've already matched against
+ // known namespace (Version 5) UUIDs above.
+ if (uuidString.startsWith(FAT_UUID_PREFIX)) {
+ String fatStr = uuidString.substring(FAT_UUID_PREFIX.length())
+ .toUpperCase(Locale.US);
+ return fatStr.substring(0, 4) + "-" + fatStr.substring(4);
+ }
+
+ return storageUuid.toString();
+ }
+ }
+
+ /**
+ * Check whether the device supports filesystem checkpoint.
+ *
+ * @return true if the device supports filesystem checkpoint, false otherwise.
+ */
+ public boolean isCheckpointSupported() {
+ try {
+ return mStorageManager.supportsCheckpoint();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reason to provide if app IO is blocked/resumed for unknown reasons
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int APP_IO_BLOCKED_REASON_UNKNOWN = 0;
+
+ /**
+ * Reason to provide if app IO is blocked/resumed because of transcoding
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 1;
+
+ /**
+ * Constants for use with
+ * {@link #notifyAppIoBlocked} and {@link notifyAppIoResumed}, to specify the reason an app's
+ * IO is blocked/resumed.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "APP_IO_BLOCKED_REASON_" }, value = {
+ APP_IO_BLOCKED_REASON_TRANSCODING,
+ APP_IO_BLOCKED_REASON_UNKNOWN,
+ })
+ public @interface AppIoBlockedReason {}
+
+ /**
+ * Notify the system that an app with {@code uid} and {@code tid} is blocked on an IO request on
+ * {@code volumeUuid} for {@code reason}.
+ *
+ * This blocked state can be used to modify the ANR behavior for the app while it's blocked.
+ * For example during transcoding.
+ *
+ * This can only be called by the {@link ExternalStorageService} holding the
+ * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+ *
+ * @param volumeUuid the UUID of the storage volume that the app IO is blocked on
+ * @param uid the UID of the app blocked on IO
+ * @param tid the tid of the app blocked on IO
+ * @param reason the reason the app is blocked on IO
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void notifyAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid,
+ @AppIoBlockedReason int reason) {
+ Objects.requireNonNull(volumeUuid);
+ try {
+ mStorageManager.notifyAppIoBlocked(convert(volumeUuid), uid, tid, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify the system that an app with {@code uid} and {@code tid} has resmued a previously
+ * blocked IO request on {@code volumeUuid} for {@code reason}.
+ *
+ * All app IO will be automatically marked as unblocked if {@code volumeUuid} is unmounted.
+ *
+ * This can only be called by the {@link ExternalStorageService} holding the
+ * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+ *
+ * @param volumeUuid the UUID of the storage volume that the app IO is resumed on
+ * @param uid the UID of the app resuming IO
+ * @param tid the tid of the app resuming IO
+ * @param reason the reason the app is resuming IO
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void notifyAppIoResumed(@NonNull UUID volumeUuid, int uid, int tid,
+ @AppIoBlockedReason int reason) {
+ Objects.requireNonNull(volumeUuid);
+ try {
+ mStorageManager.notifyAppIoResumed(convert(volumeUuid), uid, tid, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if {@code uid} with {@code tid} is blocked on IO for {@code reason}.
+ *
+ * This requires {@link ExternalStorageService} the
+ * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+ *
+ * @param volumeUuid the UUID of the storage volume to check IO blocked status
+ * @param uid the UID of the app to check IO blocked status
+ * @param tid the tid of the app to check IO blocked status
+ * @param reason the reason to check IO blocked status for
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid,
+ @AppIoBlockedReason int reason) {
+ Objects.requireNonNull(volumeUuid);
+ try {
+ return mStorageManager.isAppIoBlocked(convert(volumeUuid), uid, tid, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notify the system of the current cloud media provider.
+ *
+ * This can only be called by the {@link android.service.storage.ExternalStorageService}
+ * holding the {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+ *
+ * @param authority the authority of the content provider
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void setCloudMediaProvider(@Nullable String authority) {
+ try {
+ mStorageManager.setCloudMediaProvider(authority);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the authority of the current cloud media provider that was set by the
+ * {@link android.service.storage.ExternalStorageService} holding the
+ * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission via
+ * {@link #setCloudMediaProvider(String)}.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public String getCloudMediaProvider() {
+ try {
+ return mStorageManager.getCloudMediaProvider();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private final Object mFuseAppLoopLock = new Object();
+
+ @GuardedBy("mFuseAppLoopLock")
+ private @Nullable FuseAppLoop mFuseAppLoop = null;
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int CRYPT_TYPE_PASSWORD = 0;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int CRYPT_TYPE_DEFAULT = 1;
+}
diff --git a/android-34/android/os/storage/StorageManagerInternal.java b/android-34/android/os/storage/StorageManagerInternal.java
new file mode 100644
index 0000000..059bd84
--- /dev/null
+++ b/android-34/android/os/storage/StorageManagerInternal.java
@@ -0,0 +1,172 @@
+/*
+ * 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.UserIdInt;
+import android.os.IVold;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Mount service local interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StorageManagerInternal {
+ /**
+ * Gets the mount mode to use for a given UID
+ *
+ * @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);
+
+ /**
+ * Checks whether the {@code packageName} with {@code uid} has full external storage access via
+ * the {@link MANAGE_EXTERNAL_STORAGE} permission.
+ *
+ * @param uid the UID for which to check access.
+ * @param packageName the package in the UID for making the call.
+ * @return whether the {@code packageName} has full external storage access.
+ * Returns {@code true} if it has access, {@code false} otherwise.
+ */
+ public abstract boolean hasExternalStorageAccess(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);
+ }
+
+ /**
+ * Return true if fuse is mounted.
+ */
+ public abstract boolean isFuseMounted(int userId);
+
+ /**
+ * Create storage directories if it does not exist.
+ * Return true if the directories were setup correctly, otherwise false.
+ */
+ public abstract boolean prepareStorageDirs(int userId, Set<String> packageList,
+ String processName);
+
+ /**
+ * 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, int previousMode);
+
+ /**
+ * Asks the StorageManager to reset all state for the provided user; this will result
+ * in the unmounting for all volumes of the user, and, if the user is still running, the
+ * volumes will be re-mounted as well.
+ *
+ * @param userId the userId for which to reset storage
+ */
+ public abstract void resetUser(int userId);
+
+ /**
+ * Returns {@code true} if the immediate last installed version of an app with {@code uid} had
+ * legacy storage, {@code false} otherwise.
+ */
+ public abstract boolean hasLegacyExternalStorage(int uid);
+
+ /**
+ * Makes sure app-private data directories on external storage are setup correctly
+ * after an application is installed or upgraded. The main use for this is OBB dirs,
+ * which can be created/modified by the installer.
+ *
+ * @param packageName the package name of the package
+ * @param uid the uid of the package
+ */
+ public abstract void prepareAppDataAfterInstall(@NonNull String packageName, int uid);
+
+ /**
+ * Return true if uid is external storage service.
+ */
+ public abstract boolean isExternalStorageService(int uid);
+
+ /**
+ * Frees cache held by ExternalStorageService.
+ *
+ * <p> Blocks until the service frees the cache or fails in doing so.
+ *
+ * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed,
+ * null value indicates private internal volume.
+ * @param bytes number of bytes which need to be freed
+ */
+ public abstract void freeCache(@Nullable String volumeUuid, long bytes);
+
+ /**
+ * Returns the {@link VolumeInfo#getId()} values for the volumes matching
+ * {@link VolumeInfo#isPrimary()}
+ */
+ public abstract List<String> getPrimaryVolumeIds();
+
+ /**
+ * Tells StorageManager that CE storage for this user has been prepared.
+ *
+ * @param userId userId for which CE storage has been prepared
+ */
+ public abstract void markCeStoragePrepared(@UserIdInt int userId);
+
+ /**
+ * Returns true when CE storage for this user has been prepared.
+ *
+ * When the user key is unlocked and CE storage has been prepared,
+ * it's ok to access and modify CE directories on volumes for this user.
+ */
+ public abstract boolean isCeStoragePrepared(@UserIdInt int userId);
+
+ /**
+ * A listener for changes to the cloud provider.
+ */
+ public interface CloudProviderChangeListener {
+ /**
+ * Triggered when the cloud provider changes. A {@code null} value means there's currently
+ * no cloud provider.
+ */
+ void onCloudProviderChanged(int userId, @Nullable String authority);
+ }
+
+ /**
+ * Register a {@link CloudProviderChangeListener} to be notified when a cloud media provider
+ * changes. The listener will be called after registration with any currently set cloud media
+ * providers.
+ */
+ public abstract void registerCloudProviderChangeListener(
+ @NonNull CloudProviderChangeListener listener);
+}
diff --git a/android-34/android/os/storage/StorageVolume.java b/android-34/android/os/storage/StorageVolume.java
new file mode 100644
index 0000000..e1f112a
--- /dev/null
+++ b/android-34/android/os/storage/StorageVolume.java
@@ -0,0 +1,646 @@
+/*
+ * 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.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.compat.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 android.provider.MediaStore;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.util.Locale;
+import java.util.UUID;
+
+/**
+ * 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 mExternallyManaged;
+ private final boolean mAllowMassStorage;
+ private final long mMaxFileSize;
+ private final UserHandle mOwner;
+ private final UUID mUuid;
+ 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 externallyManaged,
+ boolean allowMassStorage, long maxFileSize, UserHandle owner, UUID uuid, 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;
+ mExternallyManaged = externallyManaged;
+ mAllowMassStorage = allowMassStorage;
+ mMaxFileSize = maxFileSize;
+ mOwner = Preconditions.checkNotNull(owner);
+ mUuid = uuid;
+ mFsUuid = fsUuid;
+ mState = Preconditions.checkNotNull(state);
+ }
+
+ private StorageVolume(Parcel in) {
+ mId = in.readString8();
+ mPath = new File(in.readString8());
+ mInternalPath = new File(in.readString8());
+ mDescription = in.readString8();
+ mPrimary = in.readInt() != 0;
+ mRemovable = in.readInt() != 0;
+ mEmulated = in.readInt() != 0;
+ mExternallyManaged = in.readInt() != 0;
+ mAllowMassStorage = in.readInt() != 0;
+ mMaxFileSize = in.readLong();
+ mOwner = in.readParcelable(null, android.os.UserHandle.class);
+ if (in.readInt() != 0) {
+ mUuid = StorageManager.convert(in.readString8());
+ } else {
+ mUuid = null;
+ }
+ mFsUuid = in.readString8();
+ mState = in.readString8();
+ }
+
+ /**
+ * Return an opaque ID that can be used to identify this volume.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the mount path for the volume.
+ *
+ * @return the mount path
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
+ @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(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
+ public File getPathFile() {
+ return mPath;
+ }
+
+ /**
+ * Returns the directory where this volume is currently mounted.
+ * <p>
+ * Direct filesystem access via this path has significant emulation
+ * overhead, and apps are instead strongly encouraged to interact with media
+ * on storage volumes via the {@link MediaStore} APIs.
+ * <p>
+ * This directory does not give apps any additional access beyond what they
+ * already have via {@link MediaStore}.
+ *
+ * @return directory where this volume is mounted, or {@code null} if the
+ * volume is not currently mounted.
+ */
+ public @Nullable File getDirectory() {
+ switch (mState) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY:
+ return mPath;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * 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 emulated
+ */
+ public boolean isEmulated() {
+ return mEmulated;
+ }
+
+ /**
+ * Returns true if the volume is managed from outside Android.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isExternallyManaged() {
+ return mExternallyManaged;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the user that owns this volume
+ */
+ // TODO(b/193460475) : Android Lint handle API change from systemApi to public Api incorrectly
+ @SuppressLint("NewApi")
+ public @NonNull UserHandle getOwner() {
+ return mOwner;
+ }
+
+ /**
+ * Gets the converted volume UUID. If a valid UUID is returned, it is compatible with other
+ * APIs that make use of {@link UUID} like {@link StorageManager#allocateBytes} and
+ * {@link android.content.pm.ApplicationInfo#storageUuid}
+ *
+ * @return the UUID for the volume or {@code null} for "portable" storage devices which haven't
+ * been adopted.
+ *
+ * @see <a href="https://source.android.com/devices/storage/adoptable">Adoptable storage</a>
+ */
+ public @Nullable UUID getStorageUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Gets the volume UUID, if any.
+ */
+ public @Nullable String getUuid() {
+ return mFsUuid;
+ }
+
+ /**
+ * Return the volume name that can be used to interact with this storage
+ * device through {@link MediaStore}.
+ *
+ * @return opaque volume name, or {@code null} if this volume is not indexed
+ * by {@link MediaStore}.
+ * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Video.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Images.Media#getContentUri(String)
+ */
+ public @Nullable String getMediaStoreVolumeName() {
+ if (isPrimary()) {
+ return MediaStore.VOLUME_EXTERNAL_PRIMARY;
+ } else {
+ return getNormalizedUuid();
+ }
+ }
+
+ /** {@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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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(@Nullable 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("mExternallyManaged", mExternallyManaged);
+ 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.writeString8(mId);
+ parcel.writeString8(mPath.toString());
+ parcel.writeString8(mInternalPath.toString());
+ parcel.writeString8(mDescription);
+ parcel.writeInt(mPrimary ? 1 : 0);
+ parcel.writeInt(mRemovable ? 1 : 0);
+ parcel.writeInt(mEmulated ? 1 : 0);
+ parcel.writeInt(mExternallyManaged ? 1 : 0);
+ parcel.writeInt(mAllowMassStorage ? 1 : 0);
+ parcel.writeLong(mMaxFileSize);
+ parcel.writeParcelable(mOwner, flags);
+ if (mUuid != null) {
+ parcel.writeInt(1);
+ parcel.writeString8(StorageManager.convert(mUuid));
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeString8(mFsUuid);
+ parcel.writeString8(mState);
+ }
+
+ /** @hide */
+ // This class is used by the mainline test suite, so we have to keep these APIs around across
+ // releases. Consider making this class public to help external developers to write tests as
+ // well.
+ @TestApi
+ public static final class Builder {
+ private String mId;
+ private File mPath;
+ private String mDescription;
+ private boolean mPrimary;
+ private boolean mRemovable;
+ private boolean mEmulated;
+ private UserHandle mOwner;
+ private UUID mStorageUuid;
+ private String mUuid;
+ private String mState;
+
+ @SuppressLint("StreamFiles")
+ public Builder(
+ @NonNull String id, @NonNull File path, @NonNull String description,
+ @NonNull UserHandle owner, @NonNull String state) {
+ mId = id;
+ mPath = path;
+ mDescription = description;
+ mOwner = owner;
+ mState = state;
+ }
+
+ @NonNull
+ public Builder setStorageUuid(@Nullable UUID storageUuid) {
+ mStorageUuid = storageUuid;
+ return this;
+ }
+
+ @NonNull
+ public Builder setUuid(@Nullable String uuid) {
+ mUuid = uuid;
+ return this;
+ }
+
+ @NonNull
+ public Builder setPrimary(boolean primary) {
+ mPrimary = primary;
+ return this;
+ }
+
+ @NonNull
+ public Builder setRemovable(boolean removable) {
+ mRemovable = removable;
+ return this;
+ }
+
+ @NonNull
+ public Builder setEmulated(boolean emulated) {
+ mEmulated = emulated;
+ return this;
+ }
+
+ @NonNull
+ public StorageVolume build() {
+ return new StorageVolume(
+ mId,
+ mPath,
+ /* internalPath= */ mPath,
+ mDescription,
+ mPrimary,
+ mRemovable,
+ mEmulated,
+ /* externallyManaged= */ false,
+ /* allowMassStorage= */ false,
+ /* maxFileSize= */ 0,
+ mOwner,
+ mStorageUuid,
+ mUuid,
+ mState);
+ }
+ }
+
+}
diff --git a/android-34/android/os/storage/VolumeInfo.java b/android-34/android/os/storage/VolumeInfo.java
new file mode 100644
index 0000000..84b6b5e
--- /dev/null
+++ b/android-34/android/os/storage/VolumeInfo.java
@@ -0,0 +1,612 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Build;
+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;
+import java.util.UUID;
+
+/**
+ * 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_FOR_READ} and
+ * {@link #MOUNT_FLAG_VISIBLE_FOR_WRITE} mean 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_FOR_READ = IVold.MOUNT_FLAG_VISIBLE_FOR_READ;
+ public static final int MOUNT_FLAG_VISIBLE_FOR_WRITE = IVold.MOUNT_FLAG_VISIBLE_FOR_WRITE;
+
+ 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public VolumeInfo(Parcel parcel) {
+ id = parcel.readString8();
+ type = parcel.readInt();
+ if (parcel.readInt() != 0) {
+ disk = DiskInfo.CREATOR.createFromParcel(parcel);
+ } else {
+ disk = null;
+ }
+ partGuid = parcel.readString8();
+ mountFlags = parcel.readInt();
+ mountUserId = parcel.readInt();
+ state = parcel.readInt();
+ fsType = parcel.readString8();
+ fsUuid = parcel.readString8();
+ fsLabel = parcel.readString8();
+ path = parcel.readString8();
+ internalPath = parcel.readString8();
+ }
+
+ public VolumeInfo(VolumeInfo volumeInfo) {
+ this.id = volumeInfo.id;
+ this.type = volumeInfo.type;
+ this.disk = volumeInfo.disk;
+ this.partGuid = volumeInfo.partGuid;
+ this.mountFlags = volumeInfo.mountFlags;
+ this.mountUserId = volumeInfo.mountUserId;
+ this.state = volumeInfo.state;
+ this.fsType = volumeInfo.fsType;
+ this.fsUuid = volumeInfo.fsUuid;
+ this.fsLabel = volumeInfo.fsLabel;
+ this.path = volumeInfo.path;
+ this.internalPath = volumeInfo.internalPath;
+ }
+
+ @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.startsWith(ID_EMULATED_INTERNAL + ";")) {
+ 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);
+ }
+
+ private boolean isVisibleForRead() {
+ return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_READ) != 0;
+ }
+
+ private boolean isVisibleForWrite() {
+ return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_WRITE) != 0;
+ }
+
+ @UnsupportedAppUsage
+ public boolean isVisible() {
+ return isVisibleForRead() || isVisibleForWrite();
+ }
+
+ private boolean isVolumeSupportedForUser(int userId) {
+ if (mountUserId != userId) {
+ return false;
+ }
+ return type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED;
+ }
+
+ /**
+ * Returns {@code true} if this volume is visible for {@code userId}, {@code false} otherwise.
+ */
+ public boolean isVisibleForUser(int userId) {
+ return isVolumeSupportedForUser(userId) && isVisible();
+ }
+
+ /**
+ * Returns {@code true} if this volume is the primary emulated volume for {@code userId},
+ * {@code false} otherwise.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean isPrimaryEmulatedForUser(int userId) {
+ return id.equals(ID_EMULATED_INTERNAL + ";" + userId);
+ }
+
+ public boolean isVisibleForRead(int userId) {
+ return isVolumeSupportedForUser(userId) && isVisibleForRead();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean isVisibleForWrite(int userId) {
+ return isVolumeSupportedForUser(userId) && isVisibleForWrite();
+ }
+
+ @UnsupportedAppUsage
+ public File getPath() {
+ return (path != null) ? new File(path) : null;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 externallyManaged = type == TYPE_STUB;
+ 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;
+ UUID uuid = 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);
+ uuid = StorageManager.convert(privateVol.fsUuid);
+ derivedFsUuid = privateVol.fsUuid;
+ } else {
+ uuid = StorageManager.UUID_DEFAULT;
+ }
+
+ if (isPrimaryEmulatedForUser(userId)) {
+ 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, externallyManaged, allowMassStorage, maxFileSize, new UserHandle(userId),
+ uuid, 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(@Nullable Object o) {
+ if (o instanceof VolumeInfo) {
+ return Objects.equals(id, ((VolumeInfo) o).id);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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.writeString8(id);
+ parcel.writeInt(type);
+ if (disk != null) {
+ parcel.writeInt(1);
+ disk.writeToParcel(parcel, flags);
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeString8(partGuid);
+ parcel.writeInt(mountFlags);
+ parcel.writeInt(mountUserId);
+ parcel.writeInt(state);
+ parcel.writeString8(fsType);
+ parcel.writeString8(fsUuid);
+ parcel.writeString8(fsLabel);
+ parcel.writeString8(path);
+ parcel.writeString8(internalPath);
+ }
+}
diff --git a/android-34/android/os/storage/VolumeRecord.java b/android-34/android/os/storage/VolumeRecord.java
new file mode 100644
index 0000000..1e5cdd7
--- /dev/null
+++ b/android-34/android/os/storage/VolumeRecord.java
@@ -0,0 +1,199 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.TimeUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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 StorageVolume buildStorageVolume(Context context) {
+ final String id = "unknown:" + fsUuid;
+ final File userPath = new File("/dev/null");
+ final File internalPath = new File("/dev/null");
+ final boolean primary = false;
+ final boolean removable = true;
+ final boolean emulated = false;
+ final boolean externallyManaged = false;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(UserHandle.USER_NULL);
+ final String envState = Environment.MEDIA_UNKNOWN;
+
+ String description = nickname;
+ if (description == null) {
+ description = context.getString(android.R.string.unknownName);
+ }
+
+ return new StorageVolume(id, userPath, internalPath, description, primary, removable,
+ emulated, externallyManaged, allowMassStorage, maxFileSize, user, null /* uuid */,
+ fsUuid, envState);
+ }
+
+ 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(@Nullable Object o) {
+ if (o instanceof VolumeRecord) {
+ return Objects.equals(fsUuid, ((VolumeRecord) o).fsUuid);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return fsUuid.hashCode();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ 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-34/android/os/strictmode/CleartextNetworkViolation.java b/android-34/android/os/strictmode/CleartextNetworkViolation.java
new file mode 100644
index 0000000..6a0d381
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/android-34/android/os/strictmode/ContentUriWithoutPermissionViolation.java
new file mode 100644
index 0000000..e78dc79
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/CredentialProtectedWhileLockedViolation.java b/android-34/android/os/strictmode/CredentialProtectedWhileLockedViolation.java
new file mode 100644
index 0000000..89cd430
--- /dev/null
+++ b/android-34/android/os/strictmode/CredentialProtectedWhileLockedViolation.java
@@ -0,0 +1,38 @@
+/*
+ * 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#createDeviceProtectedStorageContext()
+ */
+public final class CredentialProtectedWhileLockedViolation extends Violation {
+ /** @hide */
+ public CredentialProtectedWhileLockedViolation(String message) {
+ super(message);
+ }
+}
diff --git a/android-34/android/os/strictmode/CustomViolation.java b/android-34/android/os/strictmode/CustomViolation.java
new file mode 100644
index 0000000..d4ad067
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/DiskReadViolation.java b/android-34/android/os/strictmode/DiskReadViolation.java
new file mode 100644
index 0000000..fad32db
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/DiskWriteViolation.java b/android-34/android/os/strictmode/DiskWriteViolation.java
new file mode 100644
index 0000000..cb9ca38
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/ExplicitGcViolation.java b/android-34/android/os/strictmode/ExplicitGcViolation.java
new file mode 100644
index 0000000..c4ae82d
--- /dev/null
+++ b/android-34/android/os/strictmode/ExplicitGcViolation.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;
+
+import android.annotation.TestApi;
+
+/**
+ * See #{@link android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc()}.
+ */
+public final class ExplicitGcViolation extends Violation {
+ /** @hide */
+ public ExplicitGcViolation() {
+ super(null);
+ }
+}
diff --git a/android-34/android/os/strictmode/FileUriExposedViolation.java b/android-34/android/os/strictmode/FileUriExposedViolation.java
new file mode 100644
index 0000000..e3e6f83
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/ImplicitDirectBootViolation.java b/android-34/android/os/strictmode/ImplicitDirectBootViolation.java
new file mode 100644
index 0000000..e52e212
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/IncorrectContextUseViolation.java b/android-34/android/os/strictmode/IncorrectContextUseViolation.java
new file mode 100644
index 0000000..d8c22fd
--- /dev/null
+++ b/android-34/android/os/strictmode/IncorrectContextUseViolation.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.NonNull;
+import android.content.Context;
+
+/**
+ * Incorrect usage of {@link Context}, such as obtaining a UI service from non-UI {@link Context}
+ * instance.
+ *
+ * @see Context#getSystemService(String)
+ * @see Context#isUiContext
+ * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
+ */
+public final class IncorrectContextUseViolation extends Violation {
+
+ public IncorrectContextUseViolation(@NonNull String message, @NonNull Throwable originStack) {
+ super(message);
+ initCause(originStack);
+ }
+}
diff --git a/android-34/android/os/strictmode/InstanceCountViolation.java b/android-34/android/os/strictmode/InstanceCountViolation.java
new file mode 100644
index 0000000..9ee2c8e
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/IntentReceiverLeakedViolation.java b/android-34/android/os/strictmode/IntentReceiverLeakedViolation.java
new file mode 100644
index 0000000..f416c94
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/LeakedClosableViolation.java b/android-34/android/os/strictmode/LeakedClosableViolation.java
new file mode 100644
index 0000000..a2b0283
--- /dev/null
+++ b/android-34/android/os/strictmode/LeakedClosableViolation.java
@@ -0,0 +1,29 @@
+/*
+ * 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);
+ }
+
+ /** @hide */
+ public LeakedClosableViolation(String message) {
+ super(message);
+ }
+}
diff --git a/android-34/android/os/strictmode/NetworkViolation.java b/android-34/android/os/strictmode/NetworkViolation.java
new file mode 100644
index 0000000..abcf009
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/NonSdkApiUsedViolation.java b/android-34/android/os/strictmode/NonSdkApiUsedViolation.java
new file mode 100644
index 0000000..2f0cb50
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/ResourceMismatchViolation.java b/android-34/android/os/strictmode/ResourceMismatchViolation.java
new file mode 100644
index 0000000..97c4499
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/ServiceConnectionLeakedViolation.java b/android-34/android/os/strictmode/ServiceConnectionLeakedViolation.java
new file mode 100644
index 0000000..2d6b58f
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/SqliteObjectLeakedViolation.java b/android-34/android/os/strictmode/SqliteObjectLeakedViolation.java
new file mode 100644
index 0000000..0200220
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/UnbufferedIoViolation.java b/android-34/android/os/strictmode/UnbufferedIoViolation.java
new file mode 100644
index 0000000..a5c326d
--- /dev/null
+++ b/android-34/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-34/android/os/strictmode/UnsafeIntentLaunchViolation.java b/android-34/android/os/strictmode/UnsafeIntentLaunchViolation.java
new file mode 100644
index 0000000..4abdc1b
--- /dev/null
+++ b/android-34/android/os/strictmode/UnsafeIntentLaunchViolation.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+
+import java.util.Objects;
+
+/**
+ * Violation raised when your app launches an {@link Intent} which originated
+ * from outside your app.
+ * <p>
+ * Violations may indicate security vulnerabilities in the design of your app,
+ * where a malicious app could trick you into granting {@link Uri} permissions
+ * or launching unexported components. Here are some typical design patterns
+ * that can be used to safely resolve these violations:
+ * <ul>
+ * <li>The ideal approach is to migrate to using a {@link PendingIntent}, which
+ * ensures that your launch is performed using the identity of the original
+ * creator, completely avoiding the security issues described above.
+ * <li>If using a {@link PendingIntent} isn't feasible, an alternative approach
+ * is to create a brand new {@link Intent} and carefully copy only specific
+ * values from the original {@link Intent} after careful validation.
+ * </ul>
+ * <p>
+ * Note that this <em>may</em> detect false-positives if your app sends itself
+ * an {@link Intent} which is first routed through the OS, such as using
+ * {@link Intent#createChooser}. In these cases, careful inspection is required
+ * to determine if the return point into your app is appropriately protected
+ * with a signature permission or marked as unexported. If the return point is
+ * not protected, your app is likely vulnerable to malicious apps.
+ */
+public final class UnsafeIntentLaunchViolation extends Violation {
+ private transient Intent mIntent;
+
+ public UnsafeIntentLaunchViolation(@NonNull Intent intent) {
+ super("Launch of unsafe intent: " + intent);
+ mIntent = Objects.requireNonNull(intent);
+ }
+
+ /** @hide */
+ public UnsafeIntentLaunchViolation(@NonNull Intent intent, @NonNull String message) {
+ super(message);
+ mIntent = Objects.requireNonNull(intent);
+ }
+
+ /**
+ * Return the {@link Intent} which caused this violation to be raised. Note
+ * that this value is not available if this violation has been serialized
+ * since intents cannot be serialized.
+ */
+ @SuppressWarnings("IntentBuilderName")
+ public @Nullable Intent getIntent() {
+ return mIntent;
+ }
+}
diff --git a/android-34/android/os/strictmode/UntaggedSocketViolation.java b/android-34/android/os/strictmode/UntaggedSocketViolation.java
new file mode 100644
index 0000000..c34d6e8
--- /dev/null
+++ b/android-34/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.setTrafficStatsTag() to "
+ + "track all network usage");
+ }
+}
diff --git a/android-34/android/os/strictmode/Violation.java b/android-34/android/os/strictmode/Violation.java
new file mode 100644
index 0000000..0edb78a
--- /dev/null
+++ b/android-34/android/os/strictmode/Violation.java
@@ -0,0 +1,75 @@
+/*
+ * 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 {
+ private int mHashCode;
+ private boolean mHashCodeValid;
+
+ Violation(String message) {
+ super(message);
+ }
+
+ @Override
+ public int hashCode() {
+ synchronized (this) {
+ if (mHashCodeValid) {
+ return mHashCode;
+ }
+ final String message = getMessage();
+ final Throwable cause = getCause();
+ int hashCode = message != null ? message.hashCode() : getClass().hashCode();
+ hashCode = hashCode * 37 + calcStackTraceHashCode(getStackTrace());
+ hashCode = hashCode * 37 + (cause != null ? cause.toString().hashCode() : 0);
+ mHashCodeValid = true;
+ return mHashCode = hashCode;
+ }
+ }
+
+ @Override
+ public synchronized Throwable initCause(Throwable cause) {
+ mHashCodeValid = false;
+ return super.initCause(cause);
+ }
+
+ @Override
+ public void setStackTrace(StackTraceElement[] stackTrace) {
+ super.setStackTrace(stackTrace);
+ synchronized (this) {
+ mHashCodeValid = false;
+ }
+ }
+
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ mHashCodeValid = false;
+ return super.fillInStackTrace();
+ }
+
+ private static int calcStackTraceHashCode(final StackTraceElement[] stackTrace) {
+ int hashCode = 17;
+ if (stackTrace != null) {
+ for (int i = 0; i < stackTrace.length; i++) {
+ if (stackTrace[i] != null) {
+ hashCode = hashCode * 37 + stackTrace[i].hashCode();
+ }
+ }
+ }
+ return hashCode;
+ }
+}
diff --git a/android-34/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java b/android-34/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
new file mode 100644
index 0000000..c328d14
--- /dev/null
+++ b/android-34/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());
+ }
+}
diff --git a/android-34/android/os/vibrator/PrebakedSegment.java b/android-34/android/os/vibrator/PrebakedSegment.java
new file mode 100644
index 0000000..cc76ffa
--- /dev/null
+++ b/android-34/android/os/vibrator/PrebakedSegment.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrebakedSegment extends VibrationEffectSegment {
+ private final int mEffectId;
+ private final boolean mFallback;
+ private final int mEffectStrength;
+
+ PrebakedSegment(@NonNull Parcel in) {
+ mEffectId = in.readInt();
+ mFallback = in.readByte() != 0;
+ mEffectStrength = in.readInt();
+ }
+
+ /** @hide */
+ public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) {
+ mEffectId = effectId;
+ mFallback = shouldFallback;
+ mEffectStrength = effectStrength;
+ }
+
+ public int getEffectId() {
+ return mEffectId;
+ }
+
+ public int getEffectStrength() {
+ return mEffectStrength;
+ }
+
+ /** Return true if a fallback effect should be played if this effect is not supported. */
+ public boolean shouldFallback() {
+ return mFallback;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+ /** @hide */
+ @Override
+ public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ if (vibrator.areAllEffectsSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
+ return true;
+ }
+ if (!mFallback) {
+ // If the Vibrator's support is not `VIBRATION_EFFECT_SUPPORT_YES`, and this effect does
+ // not support fallbacks, the effect is considered not supported by the vibrator.
+ return false;
+ }
+ // The vibrator does not have hardware support for the effect, but the effect has fallback
+ // support. Check if a fallback will be available for the effect ID.
+ switch (mEffectId) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_TICK:
+ // Any of these effects are always supported via some form of fallback.
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ switch (mEffectId) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_POP:
+ case VibrationEffect.EFFECT_TEXTURE_TICK:
+ case VibrationEffect.EFFECT_THUD:
+ case VibrationEffect.EFFECT_TICK:
+ return true;
+ default:
+ // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback
+ return false;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ return true;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrebakedSegment resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrebakedSegment scale(float scaleFactor) {
+ // Prebaked effect strength cannot be scaled with this method.
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrebakedSegment applyEffectStrength(int effectStrength) {
+ if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
+ return new PrebakedSegment(mEffectId, mFallback, effectStrength);
+ }
+ return this;
+ }
+
+ private static boolean isValidEffectStrength(int strength) {
+ switch (strength) {
+ case VibrationEffect.EFFECT_STRENGTH_LIGHT:
+ case VibrationEffect.EFFECT_STRENGTH_MEDIUM:
+ case VibrationEffect.EFFECT_STRENGTH_STRONG:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ switch (mEffectId) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_POP:
+ case VibrationEffect.EFFECT_TEXTURE_TICK:
+ case VibrationEffect.EFFECT_THUD:
+ case VibrationEffect.EFFECT_TICK:
+ break;
+ default:
+ int[] ringtones = VibrationEffect.RINGTONES;
+ 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(@Nullable Object o) {
+ if (!(o instanceof PrebakedSegment)) {
+ return false;
+ }
+ PrebakedSegment other = (PrebakedSegment) o;
+ return mEffectId == other.mEffectId
+ && mFallback == other.mFallback
+ && mEffectStrength == other.mEffectStrength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEffectId, mFallback, mEffectStrength);
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId)
+ + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength)
+ + ", fallback=" + mFallback
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_PREBAKED);
+ out.writeInt(mEffectId);
+ out.writeByte((byte) (mFallback ? 1 : 0));
+ out.writeInt(mEffectStrength);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<PrebakedSegment> CREATOR =
+ new Parcelable.Creator<PrebakedSegment>() {
+ @Override
+ public PrebakedSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new PrebakedSegment(in);
+ }
+
+ @Override
+ public PrebakedSegment[] newArray(int size) {
+ return new PrebakedSegment[size];
+ }
+ };
+}
diff --git a/android-34/android/os/vibrator/PrimitiveSegment.java b/android-34/android/os/vibrator/PrimitiveSegment.java
new file mode 100644
index 0000000..cde0ff3
--- /dev/null
+++ b/android-34/android/os/vibrator/PrimitiveSegment.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a
+ * specified delay and applying a given scale.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrimitiveSegment extends VibrationEffectSegment {
+ private final int mPrimitiveId;
+ private final float mScale;
+ private final int mDelay;
+
+ PrimitiveSegment(@NonNull Parcel in) {
+ this(in.readInt(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public PrimitiveSegment(int id, float scale, int delay) {
+ mPrimitiveId = id;
+ mScale = scale;
+ mDelay = delay;
+ }
+
+ public int getPrimitiveId() {
+ return mPrimitiveId;
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public int getDelay() {
+ return mDelay;
+ }
+
+ @Override
+ public long getDuration() {
+ return -1;
+ }
+
+ /** @hide */
+ @Override
+ public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ return vibrator.areAllPrimitivesSupported(mPrimitiveId);
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
+ return true;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrimitiveSegment resolve(int defaultAmplitude) {
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrimitiveSegment scale(float scaleFactor) {
+ return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
+ mDelay);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrimitiveSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
+ Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
+ VibrationEffectSegment.checkDurationArgument(mDelay, "delay");
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(PARCEL_TOKEN_PRIMITIVE);
+ dest.writeInt(mPrimitiveId);
+ dest.writeFloat(mScale);
+ dest.writeInt(mDelay);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Primitive{"
+ + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId)
+ + ", scale=" + mScale
+ + ", delay=" + mDelay
+ + '}';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrimitiveSegment that = (PrimitiveSegment) o;
+ return mPrimitiveId == that.mPrimitiveId
+ && Float.compare(that.mScale, mScale) == 0
+ && mDelay == that.mDelay;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrimitiveId, mScale, mDelay);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<PrimitiveSegment> CREATOR =
+ new Parcelable.Creator<PrimitiveSegment>() {
+ @Override
+ public PrimitiveSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new PrimitiveSegment(in);
+ }
+
+ @Override
+ public PrimitiveSegment[] newArray(int size) {
+ return new PrimitiveSegment[size];
+ }
+ };
+}
diff --git a/android-34/android/os/vibrator/RampSegment.java b/android-34/android/os/vibrator/RampSegment.java
new file mode 100644
index 0000000..034962a
--- /dev/null
+++ b/android-34/android/os/vibrator/RampSegment.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency
+ * for a specified duration.
+ *
+ * <p>The amplitudes are expressed by float values in the range [0, 1], representing the relative
+ * output acceleration for the vibrator. The frequencies are expressed in hertz by positive finite
+ * float values. The special value zero is used here for an unspecified frequency, and will be
+ * automatically mapped to the device's default vibration frequency (usually the resonant
+ * frequency).
+ *
+ * @hide
+ */
+@TestApi
+public final class RampSegment extends VibrationEffectSegment {
+ private final float mStartAmplitude;
+ private final float mStartFrequencyHz;
+ private final float mEndAmplitude;
+ private final float mEndFrequencyHz;
+ private final int mDuration;
+
+ RampSegment(@NonNull Parcel in) {
+ this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public RampSegment(float startAmplitude, float endAmplitude, float startFrequencyHz,
+ float endFrequencyHz, int duration) {
+ mStartAmplitude = startAmplitude;
+ mEndAmplitude = endAmplitude;
+ mStartFrequencyHz = startFrequencyHz;
+ mEndFrequencyHz = endFrequencyHz;
+ mDuration = duration;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof RampSegment)) {
+ return false;
+ }
+ RampSegment other = (RampSegment) o;
+ return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0
+ && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0
+ && Float.compare(mStartFrequencyHz, other.mStartFrequencyHz) == 0
+ && Float.compare(mEndFrequencyHz, other.mEndFrequencyHz) == 0
+ && mDuration == other.mDuration;
+ }
+
+ public float getStartAmplitude() {
+ return mStartAmplitude;
+ }
+
+ public float getEndAmplitude() {
+ return mEndAmplitude;
+ }
+
+ public float getStartFrequencyHz() {
+ return mStartFrequencyHz;
+ }
+
+ public float getEndFrequencyHz() {
+ return mEndFrequencyHz;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ /** @hide */
+ @Override
+ public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ boolean areFeaturesSupported = true;
+ // If the start/end frequencies are not the same, require frequency control since we need to
+ // ramp up/down the frequency.
+ if ((mStartFrequencyHz != mEndFrequencyHz)
+ // If there is no frequency ramping, make sure that the one frequency used does not
+ // require frequency control.
+ || frequencyRequiresFrequencyControl(mStartFrequencyHz)) {
+ areFeaturesSupported &= vibrator.hasFrequencyControl();
+ }
+ // If the start/end amplitudes are not the same, require amplitude control since we need to
+ // ramp up/down the amplitude.
+ if ((mStartAmplitude != mEndAmplitude)
+ // If there is no amplitude ramping, make sure that the amplitude used does not
+ // require amplitude control.
+ || amplitudeRequiresAmplitudeControl(mStartAmplitude)) {
+ areFeaturesSupported &= vibrator.hasAmplitudeControl();
+ }
+ return areFeaturesSupported;
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ return mStartAmplitude > 0 || mEndAmplitude > 0;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz");
+ VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz");
+ VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
+ Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude");
+ Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public RampSegment resolve(int defaultAmplitude) {
+ // Default amplitude is not supported for ramping.
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public RampSegment scale(float scaleFactor) {
+ float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor);
+ float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor);
+ if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
+ && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
+ return this;
+ }
+ return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz,
+ mEndFrequencyHz,
+ mDuration);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public RampSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequencyHz, mEndFrequencyHz,
+ mDuration);
+ }
+
+ @Override
+ public String toString() {
+ return "Ramp{startAmplitude=" + mStartAmplitude
+ + ", endAmplitude=" + mEndAmplitude
+ + ", startFrequencyHz=" + mStartFrequencyHz
+ + ", endFrequencyHz=" + mEndFrequencyHz
+ + ", duration=" + mDuration
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_RAMP);
+ out.writeFloat(mStartAmplitude);
+ out.writeFloat(mEndAmplitude);
+ out.writeFloat(mStartFrequencyHz);
+ out.writeFloat(mEndFrequencyHz);
+ out.writeInt(mDuration);
+ }
+
+ @NonNull
+ public static final Creator<RampSegment> CREATOR =
+ new Creator<RampSegment>() {
+ @Override
+ public RampSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new RampSegment(in);
+ }
+
+ @Override
+ public RampSegment[] newArray(int size) {
+ return new RampSegment[size];
+ }
+ };
+}
diff --git a/android-34/android/os/vibrator/StepSegment.java b/android-34/android/os/vibrator/StepSegment.java
new file mode 100644
index 0000000..115a66c
--- /dev/null
+++ b/android-34/android/os/vibrator/StepSegment.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and
+ * frequency for a specified duration.
+ *
+ * <p>The amplitude is expressed by a float value in the range [0, 1], representing the relative
+ * output acceleration for the vibrator. The frequency is expressed in hertz by a positive finite
+ * float value. The special value zero is used here for an unspecified frequency, and will be
+ * automatically mapped to the device's default vibration frequency (usually the resonant
+ * frequency).
+ *
+ * @hide
+ */
+@TestApi
+public final class StepSegment extends VibrationEffectSegment {
+ private final float mAmplitude;
+ private final float mFrequencyHz;
+ private final int mDuration;
+
+ StepSegment(@NonNull Parcel in) {
+ this(in.readFloat(), in.readFloat(), in.readInt());
+ }
+
+ /** @hide */
+ public StepSegment(float amplitude, float frequencyHz, int duration) {
+ mAmplitude = amplitude;
+ mFrequencyHz = frequencyHz;
+ mDuration = duration;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof StepSegment)) {
+ return false;
+ }
+ StepSegment other = (StepSegment) o;
+ return Float.compare(mAmplitude, other.mAmplitude) == 0
+ && Float.compare(mFrequencyHz, other.mFrequencyHz) == 0
+ && mDuration == other.mDuration;
+ }
+
+ public float getAmplitude() {
+ return mAmplitude;
+ }
+
+ public float getFrequencyHz() {
+ return mFrequencyHz;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ /** @hide */
+ @Override
+ public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ boolean areFeaturesSupported = true;
+ if (frequencyRequiresFrequencyControl(mFrequencyHz)) {
+ areFeaturesSupported &= vibrator.hasFrequencyControl();
+ }
+ if (amplitudeRequiresAmplitudeControl(mAmplitude)) {
+ areFeaturesSupported &= vibrator.hasAmplitudeControl();
+ }
+ return areFeaturesSupported;
+ }
+
+ /** @hide */
+ @Override
+ public boolean isHapticFeedbackCandidate() {
+ return true;
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasNonZeroAmplitude() {
+ // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
+ return Float.compare(mAmplitude, 0) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ VibrationEffectSegment.checkFrequencyArgument(mFrequencyHz, "frequencyHz");
+ VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+ Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public StepSegment resolve(int defaultAmplitude) {
+ if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) {
+ throw new IllegalArgumentException(
+ "amplitude must be between 1 and 255 inclusive (amplitude="
+ + defaultAmplitude + ")");
+ }
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+ return this;
+ }
+ return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE,
+ mFrequencyHz,
+ mDuration);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public StepSegment scale(float scaleFactor) {
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+ return this;
+ }
+ return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequencyHz,
+ mDuration);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public StepSegment applyEffectStrength(int effectStrength) {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAmplitude, mFrequencyHz, mDuration);
+ }
+
+ @Override
+ public String toString() {
+ return "Step{amplitude=" + mAmplitude
+ + ", frequencyHz=" + mFrequencyHz
+ + ", duration=" + mDuration
+ + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_STEP);
+ out.writeFloat(mAmplitude);
+ out.writeFloat(mFrequencyHz);
+ out.writeInt(mDuration);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<StepSegment> CREATOR =
+ new Parcelable.Creator<StepSegment>() {
+ @Override
+ public StepSegment createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new StepSegment(in);
+ }
+
+ @Override
+ public StepSegment[] newArray(int size) {
+ return new StepSegment[size];
+ }
+ };
+}
diff --git a/android-34/android/os/vibrator/VibrationConfig.java b/android-34/android/os/vibrator/VibrationConfig.java
new file mode 100644
index 0000000..4a61472
--- /dev/null
+++ b/android-34/android/os/vibrator/VibrationConfig.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.os.Vibrator.VibrationIntensity;
+
+/**
+ * List of device-specific internal vibration configuration loaded from platform config.xml.
+ *
+ * <p>This should not be public, but some individual values are exposed by {@link Vibrator} by
+ * hidden methods, made available to Settings, SysUI and other platform client code. They can also
+ * be individually exposed with the necessary permissions by the {@link Vibrator} service.
+ *
+ * @hide
+ */
+public class VibrationConfig {
+
+ // TODO(b/191150049): move these to vibrator static config file
+ private final float mHapticChannelMaxVibrationAmplitude;
+ private final int mRampStepDurationMs;
+ private final int mRampDownDurationMs;
+
+ @VibrationIntensity
+ private final int mDefaultAlarmVibrationIntensity;
+ @VibrationIntensity
+ private final int mDefaultHapticFeedbackIntensity;
+ @VibrationIntensity
+ private final int mDefaultMediaVibrationIntensity;
+ @VibrationIntensity
+ private final int mDefaultNotificationVibrationIntensity;
+ @VibrationIntensity
+ private final int mDefaultRingVibrationIntensity;
+
+ /** @hide */
+ public VibrationConfig(@Nullable Resources resources) {
+ mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
+ com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude, 0);
+ mRampDownDurationMs = loadInteger(resources,
+ com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
+ mRampStepDurationMs = loadInteger(resources,
+ com.android.internal.R.integer.config_vibrationWaveformRampStepDuration, 0);
+
+ mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
+ com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
+ mDefaultHapticFeedbackIntensity = loadDefaultIntensity(resources,
+ com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
+ mDefaultMediaVibrationIntensity = loadDefaultIntensity(resources,
+ com.android.internal.R.integer.config_defaultMediaVibrationIntensity);
+ mDefaultNotificationVibrationIntensity = loadDefaultIntensity(resources,
+ com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+ mDefaultRingVibrationIntensity = loadDefaultIntensity(resources,
+ com.android.internal.R.integer.config_defaultRingVibrationIntensity);
+ }
+
+ @VibrationIntensity
+ private static int loadDefaultIntensity(@Nullable Resources res, int resId) {
+ int defaultIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
+ int value = loadInteger(res, resId, defaultIntensity);
+ if (value < Vibrator.VIBRATION_INTENSITY_OFF || value > Vibrator.VIBRATION_INTENSITY_HIGH) {
+ return defaultIntensity;
+ }
+ return value;
+ }
+
+ private static float loadFloat(@Nullable Resources res, int resId, float defaultValue) {
+ return res != null ? res.getFloat(resId) : defaultValue;
+ }
+
+ private static int loadInteger(@Nullable Resources res, int resId, int defaultValue) {
+ return res != null ? res.getInteger(resId) : defaultValue;
+ }
+
+ /**
+ * Return the maximum amplitude the vibrator can play using the audio haptic channels.
+ *
+ * @return a positive value representing the maximum absolute value the device can play signals
+ * from audio haptic channels, or {@link Float#NaN NaN} if it's unknown.
+ */
+ public float getHapticChannelMaximumAmplitude() {
+ if (mHapticChannelMaxVibrationAmplitude <= 0) {
+ return Float.NaN;
+ }
+ return mHapticChannelMaxVibrationAmplitude;
+ }
+
+ /**
+ * The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
+ * when a vibration is cancelled or finished at non-zero amplitude.
+ */
+ public int getRampDownDurationMs() {
+ if (mRampDownDurationMs < 0) {
+ return 0;
+ }
+ return mRampDownDurationMs;
+ }
+
+ /**
+ * The duration, in milliseconds, that should be applied to convert vibration effect's
+ * {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
+ * devices without PWLE support.
+ */
+ public int getRampStepDurationMs() {
+ if (mRampStepDurationMs < 0) {
+ return 0;
+ }
+ return mRampStepDurationMs;
+ }
+
+ /** Get the default vibration intensity for given usage. */
+ @VibrationIntensity
+ public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case USAGE_ALARM:
+ return mDefaultAlarmVibrationIntensity;
+ case USAGE_NOTIFICATION:
+ case USAGE_COMMUNICATION_REQUEST:
+ return mDefaultNotificationVibrationIntensity;
+ case USAGE_RINGTONE:
+ return mDefaultRingVibrationIntensity;
+ case USAGE_TOUCH:
+ case USAGE_HARDWARE_FEEDBACK:
+ case USAGE_PHYSICAL_EMULATION:
+ case USAGE_ACCESSIBILITY:
+ return mDefaultHapticFeedbackIntensity;
+ case USAGE_MEDIA:
+ case USAGE_UNKNOWN:
+ // fall through
+ default:
+ return mDefaultMediaVibrationIntensity;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "VibrationConfig{"
+ + "mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
+ + ", mRampStepDurationMs=" + mRampStepDurationMs
+ + ", mRampDownDurationMs=" + mRampDownDurationMs
+ + ", mDefaultAlarmIntensity=" + mDefaultAlarmVibrationIntensity
+ + ", mDefaultHapticFeedbackIntensity=" + mDefaultHapticFeedbackIntensity
+ + ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
+ + ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
+ + ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ + "}";
+ }
+}
diff --git a/android-34/android/os/vibrator/VibrationEffectSegment.java b/android-34/android/os/vibrator/VibrationEffectSegment.java
new file mode 100644
index 0000000..75a055f
--- /dev/null
+++ b/android-34/android/os/vibrator/VibrationEffectSegment.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+/**
+ * Representation of a single segment of a {@link VibrationEffect}.
+ *
+ * <p>Vibration effects are represented as a sequence of segments that describes how vibration
+ * amplitude and frequency changes over time. Segments can be described as one of the following:
+ *
+ * <ol>
+ * <li>A predefined vibration effect;
+ * <li>A composable effect primitive;
+ * <li>Fixed amplitude and frequency values to be held for a specified duration;
+ * <li>Pairs of amplitude and frequency values to be ramped to for a specified duration;
+ * </ol>
+ *
+ * @hide
+ */
+@TestApi
+@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
+public abstract class VibrationEffectSegment implements Parcelable {
+ static final int PARCEL_TOKEN_PREBAKED = 1;
+ static final int PARCEL_TOKEN_PRIMITIVE = 2;
+ static final int PARCEL_TOKEN_STEP = 3;
+ static final int PARCEL_TOKEN_RAMP = 4;
+
+ /** Prevent subclassing from outside of this package */
+ VibrationEffectSegment() {
+ }
+
+ /**
+ * Gets the estimated duration of the segment in milliseconds.
+ *
+ * <p>For segments with an unknown duration (e.g. prebaked or primitive effects where the length
+ * is device and potentially run-time dependent), this returns -1.
+ */
+ public abstract long getDuration();
+
+ /**
+ * Checks if a given {@link Vibrator} can play this segment as intended. See
+ * {@link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information about
+ * what counts as supported by a vibrator, and what counts as not.
+ *
+ * @hide
+ */
+ public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+
+ /**
+ * Returns true if this segment could be a haptic feedback effect candidate.
+ *
+ * @see VibrationEffect#isHapticFeedbackCandidate()
+ * @hide
+ */
+ public abstract boolean isHapticFeedbackCandidate();
+
+ /**
+ * Returns true if this segment plays at a non-zero amplitude at some point.
+ *
+ * @hide
+ */
+ public abstract boolean hasNonZeroAmplitude();
+
+ /**
+ * Validates the segment, throwing exceptions if any parameter is invalid.
+ *
+ * @hide
+ */
+ public abstract void validate();
+
+ /**
+ * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}.
+ *
+ * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger
+ * than {@link VibrationEffect#MAX_AMPLITUDE}.
+ *
+ * @hide
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude);
+
+ /**
+ * Scale the segment intensity with the given factor.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ *
+ * @hide
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
+
+ /**
+ * Applies given effect strength to prebaked effects.
+ *
+ * @param effectStrength new effect strength to be applied, one of
+ * VibrationEffect.EFFECT_STRENGTH_*.
+ *
+ * @hide
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
+
+ /**
+ * Checks the given frequency argument is valid to represent a vibration effect frequency in
+ * hertz, i.e. a finite non-negative value.
+ *
+ * @param value the frequency argument value to be checked
+ * @param name the argument name for the error message.
+ *
+ * @hide
+ */
+ public static void checkFrequencyArgument(float value, @NonNull String name) {
+ // Similar to combining Preconditions checkArgumentFinite + checkArgumentNonnegative,
+ // but this implementation doesn't create the error message unless a check fail.
+ if (Float.isNaN(value)) {
+ throw new IllegalArgumentException(name + " must not be NaN");
+ }
+ if (Float.isInfinite(value)) {
+ throw new IllegalArgumentException(name + " must not be infinite");
+ }
+ if (value < 0) {
+ throw new IllegalArgumentException(name + " must be >= 0, got " + value);
+ }
+ }
+
+ /**
+ * Checks the given duration argument is valid, i.e. a non-negative value.
+ *
+ * @param value the duration value to be checked
+ * @param name the argument name for the error message.
+ *
+ * @hide
+ */
+ public static void checkDurationArgument(long value, @NonNull String name) {
+ if (value < 0) {
+ throw new IllegalArgumentException(name + " must be >= 0, got " + value);
+ }
+ }
+
+ /**
+ * Helper method to check if an amplitude requires a vibrator to have amplitude control to play.
+ *
+ * @hide
+ */
+ protected static boolean amplitudeRequiresAmplitudeControl(float amplitude) {
+ return (amplitude != 0)
+ && (amplitude != 1)
+ && (amplitude != VibrationEffect.DEFAULT_AMPLITUDE);
+ }
+
+ /**
+ * Helper method to check if a frequency requires a vibrator to have frequency control to play.
+ *
+ * @hide
+ */
+ protected static boolean frequencyRequiresFrequencyControl(float frequency) {
+ // Anything other than the default frequency value (represented with "0") requires frequency
+ // control.
+ return frequency != 0;
+ }
+
+ @NonNull
+ public static final Creator<VibrationEffectSegment> CREATOR =
+ new Creator<VibrationEffectSegment>() {
+ @Override
+ public VibrationEffectSegment createFromParcel(Parcel in) {
+ switch (in.readInt()) {
+ case PARCEL_TOKEN_STEP:
+ return new StepSegment(in);
+ case PARCEL_TOKEN_RAMP:
+ return new RampSegment(in);
+ case PARCEL_TOKEN_PREBAKED:
+ return new PrebakedSegment(in);
+ case PARCEL_TOKEN_PRIMITIVE:
+ return new PrimitiveSegment(in);
+ default:
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+
+ @Override
+ public VibrationEffectSegment[] newArray(int size) {
+ return new VibrationEffectSegment[size];
+ }
+ };
+}
diff --git a/android-34/android/os/vibrator/VibratorFrequencyProfile.java b/android-34/android/os/vibrator/VibratorFrequencyProfile.java
new file mode 100644
index 0000000..8392940
--- /dev/null
+++ b/android-34/android/os/vibrator/VibratorFrequencyProfile.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.VibratorInfo;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Describes the output of a {@link android.os.Vibrator} for different vibration frequencies.
+ *
+ * <p>The profile contains the minimum and maximum supported vibration frequencies, if the device
+ * supports independent frequency control.
+ *
+ * <p>It also describes the relative output acceleration of a vibration at different supported
+ * frequencies. The acceleration is defined by a relative amplitude value between 0 and 1,
+ * inclusive, where 0 represents the vibrator off state and 1 represents the maximum output
+ * acceleration that the vibrator can reach across all supported frequencies.
+ *
+ * <p>The measurements are returned as an array of uniformly distributed amplitude values for
+ * frequencies between the minimum and maximum supported ones. The measurement interval is the
+ * frequency increment between each pair of amplitude values.
+ *
+ * <p>Vibrators without independent frequency control do not have a frequency profile.
+ * @hide
+ */
+@TestApi
+public final class VibratorFrequencyProfile {
+
+ private final VibratorInfo.FrequencyProfile mFrequencyProfile;
+
+ /** @hide */
+ public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) {
+ Preconditions.checkArgument(!frequencyProfile.isEmpty(),
+ "Frequency profile must have a non-empty frequency range");
+ mFrequencyProfile = frequencyProfile;
+ }
+
+ /**
+ * Measurements of the maximum relative amplitude the vibrator can achieve for each supported
+ * frequency.
+ *
+ * <p>The frequency of a measurement is determined as:
+ *
+ * {@code getMinFrequency() + measurementIndex * getMaxAmplitudeMeasurementInterval()}
+ *
+ * <p>The returned list will not be empty, and will have entries representing frequencies from
+ * {@link #getMinFrequency()} to {@link #getMaxFrequency()}, inclusive.
+ *
+ * @return Array of maximum relative amplitude measurements.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @FloatRange(from = 0, to = 1)
+ public float[] getMaxAmplitudeMeasurements() {
+ // VibratorInfo getters always return a copy or clone of the data objects.
+ return mFrequencyProfile.getMaxAmplitudes();
+ }
+
+ /**
+ * Gets the frequency interval used to measure the maximum relative amplitudes.
+ *
+ * @return the frequency interval used for the measurement, in hertz.
+ * @hide
+ */
+ @TestApi
+ public float getMaxAmplitudeMeasurementInterval() {
+ return mFrequencyProfile.getFrequencyResolutionHz();
+ }
+
+ /**
+ * Gets the minimum frequency supported by the vibrator.
+ *
+ * @return the minimum frequency supported by the vibrator, in hertz.
+ * @hide
+ */
+ @TestApi
+ public float getMinFrequency() {
+ return mFrequencyProfile.getFrequencyRangeHz().getLower();
+ }
+
+ /**
+ * Gets the maximum frequency supported by the vibrator.
+ *
+ * @return the maximum frequency supported by the vibrator, in hertz.
+ * @hide
+ */
+ @TestApi
+ public float getMaxFrequency() {
+ return mFrequencyProfile.getFrequencyRangeHz().getUpper();
+ }
+}