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/accessibilityservice/AccessibilityButtonController.java b/android-34/android/accessibilityservice/AccessibilityButtonController.java
new file mode 100644
index 0000000..2ccad1d
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityButtonController.java
@@ -0,0 +1,220 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/**
+ * Controller for the accessibility button within the system's navigation area
+ * <p>
+ * This class may be used to query the accessibility button's state and register
+ * callbacks for interactions with and state changes to the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class and
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as
+ * the sole means for offering functionality to users via an {@link AccessibilityService}.
+ * Some device implementations may choose not to provide a software-rendered system
+ * navigation area, making this affordance permanently unavailable.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> On device implementations where the accessibility button is
+ * supported, it may not be available at all times, such as when a foreground application uses
+ * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign
+ * this button to another accessibility service or feature. In each of these cases, a
+ * registered {@link AccessibilityButtonCallback}'s
+ * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)}
+ * method will be invoked to provide notifications of changes in the accessibility button's
+ * availability to the registering service.
+ * </p>
+ */
+public final class AccessibilityButtonController {
+ private static final String LOG_TAG = "A11yButtonController";
+
+ private final IAccessibilityServiceConnection mServiceConnection;
+ private final Object mLock;
+ private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks;
+
+ AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) {
+ mServiceConnection = serviceConnection;
+ mLock = new Object();
+ }
+
+ /**
+ * Retrieves whether the accessibility button in the system's navigation area is
+ * available to the calling service.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will have no effect and return {@code false}.
+ * </p>
+ *
+ * @return {@code true} if the accessibility button in the system's navigation area is
+ * available to the calling service, {@code false} otherwise
+ */
+ public boolean isAccessibilityButtonAvailable() {
+ if (mServiceConnection != null) {
+ try {
+ return mServiceConnection.isAccessibilityButtonAvailable();
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re);
+ re.rethrowFromSystemServer();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+ * changes callbacks related to the accessibility button.
+ *
+ * @param callback the callback to add, must be non-null
+ */
+ public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) {
+ registerAccessibilityButtonCallback(callback, new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+ * change callbacks related to the accessibility button. The callback will occur on the
+ * specified {@link Handler}'s thread, or on the services's main thread if the handler is
+ * {@code null}.
+ *
+ * @param callback the callback to add, must be non-null
+ * @param handler the handler on which the callback should execute, must be non-null
+ */
+ public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback,
+ @NonNull Handler handler) {
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(handler);
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ mCallbacks = new ArrayMap<>();
+ }
+
+ mCallbacks.put(callback, handler);
+ }
+ }
+
+ /**
+ * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state
+ * change callbacks related to the accessibility button.
+ *
+ * @param callback the callback to remove, must be non-null
+ */
+ public void unregisterAccessibilityButtonCallback(
+ @NonNull AccessibilityButtonCallback callback) {
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ return;
+ }
+
+ final int keyIndex = mCallbacks.indexOfKey(callback);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mCallbacks.removeAt(keyIndex);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the accessibility button click to any registered callbacks. This should
+ * be called on the service's main thread.
+ */
+ void dispatchAccessibilityButtonClicked() {
+ final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+ synchronized (mLock) {
+ if (mCallbacks == null || mCallbacks.isEmpty()) {
+ Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!");
+ return;
+ }
+
+ // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final AccessibilityButtonCallback callback = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ handler.post(() -> callback.onClicked(this));
+ }
+ }
+
+ /**
+ * Dispatches the accessibility button availability changes to any registered callbacks.
+ * This should be called on the service's main thread.
+ */
+ void dispatchAccessibilityButtonAvailabilityChanged(boolean available) {
+ final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+ synchronized (mLock) {
+ if (mCallbacks == null || mCallbacks.isEmpty()) {
+ Slog.w(LOG_TAG,
+ "Received accessibility button availability change with no callbacks!");
+ return;
+ }
+
+ // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final AccessibilityButtonCallback callback = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ handler.post(() -> callback.onAvailabilityChanged(this, available));
+ }
+ }
+
+ /**
+ * Callback for interaction with and changes to state of the accessibility button
+ * within the system's navigation area.
+ */
+ public static abstract class AccessibilityButtonCallback {
+
+ /**
+ * Called when the accessibility button in the system's navigation area is clicked.
+ *
+ * @param controller the controller used to register for this callback
+ */
+ public void onClicked(AccessibilityButtonController controller) {}
+
+ /**
+ * Called when the availability of the accessibility button in the system's
+ * navigation area has changed. The accessibility button may become unavailable
+ * because the device shopped showing the button, the button was assigned to another
+ * service, or for other reasons.
+ *
+ * @param controller the controller used to register for this callback
+ * @param available {@code true} if the accessibility button is available to this
+ * service, {@code false} otherwise
+ */
+ public void onAvailabilityChanged(AccessibilityButtonController controller,
+ boolean available) {
+ }
+ }
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityGestureEvent.java b/android-34/android/accessibilityservice/AccessibilityGestureEvent.java
new file mode 100644
index 0000000..8e01779
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -0,0 +1,323 @@
+/*
+ * 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.accessibilityservice;
+
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_PASSTHROUGH;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_TOUCH_EXPLORATION;
+import static android.accessibilityservice.AccessibilityService.GESTURE_UNKNOWN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class describes the gesture event including gesture id and which display it happens
+ * on.
+ * <p>
+ * <strong>Note:</strong> Accessibility services setting the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag can receive gestures.
+ *
+ * @see AccessibilityService#onGesture(AccessibilityGestureEvent)
+ */
+
+public final class AccessibilityGestureEvent implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = { "GESTURE_" }, value = {
+ GESTURE_UNKNOWN,
+ GESTURE_TOUCH_EXPLORATION,
+ GESTURE_2_FINGER_SINGLE_TAP,
+ GESTURE_2_FINGER_DOUBLE_TAP,
+ GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
+ GESTURE_2_FINGER_TRIPLE_TAP,
+ GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD,
+ GESTURE_3_FINGER_SINGLE_TAP,
+ GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
+ GESTURE_3_FINGER_DOUBLE_TAP,
+ GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
+ GESTURE_3_FINGER_TRIPLE_TAP,
+ GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD,
+ GESTURE_DOUBLE_TAP,
+ GESTURE_DOUBLE_TAP_AND_HOLD,
+ GESTURE_SWIPE_UP,
+ GESTURE_SWIPE_UP_AND_LEFT,
+ GESTURE_SWIPE_UP_AND_DOWN,
+ GESTURE_SWIPE_UP_AND_RIGHT,
+ GESTURE_SWIPE_DOWN,
+ GESTURE_SWIPE_DOWN_AND_LEFT,
+ GESTURE_SWIPE_DOWN_AND_UP,
+ GESTURE_SWIPE_DOWN_AND_RIGHT,
+ GESTURE_SWIPE_LEFT,
+ GESTURE_SWIPE_LEFT_AND_UP,
+ GESTURE_SWIPE_LEFT_AND_RIGHT,
+ GESTURE_SWIPE_LEFT_AND_DOWN,
+ GESTURE_SWIPE_RIGHT,
+ GESTURE_SWIPE_RIGHT_AND_UP,
+ GESTURE_SWIPE_RIGHT_AND_LEFT,
+ GESTURE_SWIPE_RIGHT_AND_DOWN,
+ GESTURE_2_FINGER_SWIPE_DOWN,
+ GESTURE_2_FINGER_SWIPE_LEFT,
+ GESTURE_2_FINGER_SWIPE_RIGHT,
+ GESTURE_2_FINGER_SWIPE_UP,
+ GESTURE_3_FINGER_SWIPE_DOWN,
+ GESTURE_3_FINGER_SWIPE_LEFT,
+ GESTURE_3_FINGER_SWIPE_RIGHT,
+ GESTURE_3_FINGER_SWIPE_UP,
+ GESTURE_4_FINGER_DOUBLE_TAP,
+ GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD,
+ GESTURE_4_FINGER_SINGLE_TAP,
+ GESTURE_4_FINGER_SWIPE_DOWN,
+ GESTURE_4_FINGER_SWIPE_LEFT,
+ GESTURE_4_FINGER_SWIPE_RIGHT,
+ GESTURE_4_FINGER_SWIPE_UP,
+ GESTURE_4_FINGER_TRIPLE_TAP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GestureId {}
+
+ @GestureId
+ private final int mGestureId;
+ private final int mDisplayId;
+ private List<MotionEvent> mMotionEvents = new ArrayList<>();
+
+/**
+ * Constructs an AccessibilityGestureEvent to be dispatched to an accessibility service.
+ * @param gestureId the id number of the gesture.
+ * @param displayId the display on which this gesture was performed.
+ * @param motionEvents the motion events that lead to this gesture.
+ */
+ public AccessibilityGestureEvent(
+ int gestureId, int displayId, @NonNull List<MotionEvent> motionEvents) {
+ mGestureId = gestureId;
+ mDisplayId = displayId;
+ mMotionEvents.addAll(motionEvents);
+ }
+
+ /** @hide */
+ @TestApi
+ public AccessibilityGestureEvent(int gestureId, int displayId) {
+ this(gestureId, displayId, new ArrayList<MotionEvent>());
+ }
+
+ private AccessibilityGestureEvent(@NonNull Parcel parcel) {
+ mGestureId = parcel.readInt();
+ mDisplayId = parcel.readInt();
+ ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader(), android.content.pm.ParceledListSlice.class);
+ mMotionEvents = slice.getList();
+ }
+
+ /**
+ * Returns the display id of the received-gesture display, for use with
+ * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+ *
+ * @return the display id.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Returns performed gesture id.
+ *
+ * @return the performed gesture id.
+ *
+ */
+ @GestureId public int getGestureId() {
+ return mGestureId;
+ }
+
+ /**
+ * Returns the motion events that lead to this gesture.
+ *
+ */
+ @NonNull
+ public List<MotionEvent> getMotionEvents() {
+ return mMotionEvents;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent[");
+ stringBuilder.append("gestureId: ").append(gestureIdToString(mGestureId));
+ stringBuilder.append(", ");
+ stringBuilder.append("displayId: ").append(mDisplayId);
+ stringBuilder.append(", ");
+ stringBuilder.append("Motion Events: [");
+ for (int i = 0; i < mMotionEvents.size(); ++i) {
+ String action = MotionEvent.actionToString(mMotionEvents.get(i).getActionMasked());
+ stringBuilder.append(action);
+ if (i < (mMotionEvents.size() - 1)) {
+ stringBuilder.append(", ");
+ } else {
+ stringBuilder.append("]");
+ }
+ }
+ stringBuilder.append(']');
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Returns a string representation of the specified gesture id.
+ */
+ @NonNull
+ public static String gestureIdToString(int id) {
+ switch (id) {
+ case GESTURE_UNKNOWN: return "GESTURE_UNKNOWN";
+ case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH";
+ case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION";
+ case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
+ case GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD";
+ case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
+ case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
+ case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
+ case GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD";
+ case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
+ case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
+ case GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD";
+ case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
+ case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+ case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
+ return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
+ case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
+ case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
+ case GESTURE_SWIPE_DOWN: return "GESTURE_SWIPE_DOWN";
+ case GESTURE_SWIPE_DOWN_AND_LEFT: return "GESTURE_SWIPE_DOWN_AND_LEFT";
+ case GESTURE_SWIPE_DOWN_AND_UP: return "GESTURE_SWIPE_DOWN_AND_UP";
+ case GESTURE_SWIPE_DOWN_AND_RIGHT: return "GESTURE_SWIPE_DOWN_AND_RIGHT";
+ case GESTURE_SWIPE_LEFT: return "GESTURE_SWIPE_LEFT";
+ case GESTURE_SWIPE_LEFT_AND_UP: return "GESTURE_SWIPE_LEFT_AND_UP";
+ case GESTURE_SWIPE_LEFT_AND_RIGHT: return "GESTURE_SWIPE_LEFT_AND_RIGHT";
+ case GESTURE_SWIPE_LEFT_AND_DOWN: return "GESTURE_SWIPE_LEFT_AND_DOWN";
+ case GESTURE_SWIPE_RIGHT: return "GESTURE_SWIPE_RIGHT";
+ case GESTURE_SWIPE_RIGHT_AND_UP: return "GESTURE_SWIPE_RIGHT_AND_UP";
+ case GESTURE_SWIPE_RIGHT_AND_LEFT: return "GESTURE_SWIPE_RIGHT_AND_LEFT";
+ case GESTURE_SWIPE_RIGHT_AND_DOWN: return "GESTURE_SWIPE_RIGHT_AND_DOWN";
+ case GESTURE_SWIPE_UP: return "GESTURE_SWIPE_UP";
+ case GESTURE_SWIPE_UP_AND_LEFT: return "GESTURE_SWIPE_UP_AND_LEFT";
+ case GESTURE_SWIPE_UP_AND_DOWN: return "GESTURE_SWIPE_UP_AND_DOWN";
+ case GESTURE_SWIPE_UP_AND_RIGHT: return "GESTURE_SWIPE_UP_AND_RIGHT";
+ case GESTURE_2_FINGER_SWIPE_DOWN: return "GESTURE_2_FINGER_SWIPE_DOWN";
+ case GESTURE_2_FINGER_SWIPE_LEFT: return "GESTURE_2_FINGER_SWIPE_LEFT";
+ case GESTURE_2_FINGER_SWIPE_RIGHT: return "GESTURE_2_FINGER_SWIPE_RIGHT";
+ case GESTURE_2_FINGER_SWIPE_UP: return "GESTURE_2_FINGER_SWIPE_UP";
+ case GESTURE_3_FINGER_SWIPE_DOWN: return "GESTURE_3_FINGER_SWIPE_DOWN";
+ case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT";
+ case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT";
+ case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP";
+ case GESTURE_4_FINGER_SWIPE_DOWN: return "GESTURE_4_FINGER_SWIPE_DOWN";
+ case GESTURE_4_FINGER_SWIPE_LEFT: return "GESTURE_4_FINGER_SWIPE_LEFT";
+ case GESTURE_4_FINGER_SWIPE_RIGHT: return "GESTURE_4_FINGER_SWIPE_RIGHT";
+ case GESTURE_4_FINGER_SWIPE_UP: return "GESTURE_4_FINGER_SWIPE_UP";
+ default: return Integer.toHexString(id);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mGestureId);
+ parcel.writeInt(mDisplayId);
+ parcel.writeParcelable(new ParceledListSlice<MotionEvent>(mMotionEvents), 0);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @NonNull Parcelable.Creator<AccessibilityGestureEvent> CREATOR =
+ new Parcelable.Creator<AccessibilityGestureEvent>() {
+ public AccessibilityGestureEvent createFromParcel(Parcel parcel) {
+ return new AccessibilityGestureEvent(parcel);
+ }
+
+ public AccessibilityGestureEvent[] newArray(int size) {
+ return new AccessibilityGestureEvent[size];
+ }
+ };
+
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityInputMethodSession.java b/android-34/android/accessibilityservice/AccessibilityInputMethodSession.java
new file mode 100644
index 0000000..ecf449d
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityInputMethodSession.java
@@ -0,0 +1,33 @@
+/*
+ * 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.accessibilityservice;
+
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+
+interface AccessibilityInputMethodSession {
+ void finishInput();
+
+ void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
+ int candidatesStart, int candidatesEnd);
+
+ void invalidateInput(EditorInfo editorInfo, IRemoteAccessibilityInputConnection connection,
+ int sessionId);
+
+ void setEnabled(boolean enabled);
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java b/android-34/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java
new file mode 100644
index 0000000..3252ab2
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityInputMethodSessionWrapper.java
@@ -0,0 +1,116 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+final class AccessibilityInputMethodSessionWrapper extends IAccessibilityInputMethodSession.Stub {
+ private final Handler mHandler;
+
+ @NonNull
+ private final AtomicReference<AccessibilityInputMethodSession> mSessionRef;
+
+ AccessibilityInputMethodSessionWrapper(
+ @NonNull Looper looper, @NonNull AccessibilityInputMethodSession session) {
+ mSessionRef = new AtomicReference<>(session);
+ mHandler = Handler.createAsync(looper);
+ }
+
+ @AnyThread
+ @Nullable
+ AccessibilityInputMethodSession getSession() {
+ return mSessionRef.get();
+ }
+
+ @Override
+ public void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
+ candidatesEnd);
+ } else {
+ mHandler.post(() -> doUpdateSelection(oldSelStart, oldSelEnd, newSelStart,
+ newSelEnd, candidatesStart, candidatesEnd));
+ }
+ }
+
+ private void doUpdateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
+ final AccessibilityInputMethodSession session = mSessionRef.get();
+ if (session != null) {
+ session.updateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
+ candidatesEnd);
+ }
+ }
+
+ @Override
+ public void finishInput() {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doFinishInput();
+ } else {
+ mHandler.post(this::doFinishInput);
+ }
+ }
+
+ private void doFinishInput() {
+ final AccessibilityInputMethodSession session = mSessionRef.get();
+ if (session != null) {
+ session.finishInput();
+ }
+ }
+
+ @Override
+ public void finishSession() {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doFinishSession();
+ } else {
+ mHandler.post(this::doFinishSession);
+ }
+ }
+
+ private void doFinishSession() {
+ mSessionRef.set(null);
+ }
+
+ @Override
+ public void invalidateInput(EditorInfo editorInfo,
+ IRemoteAccessibilityInputConnection connection, int sessionId) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ doInvalidateInput(editorInfo, connection, sessionId);
+ } else {
+ mHandler.post(() -> doInvalidateInput(editorInfo, connection, sessionId));
+ }
+ }
+
+ private void doInvalidateInput(EditorInfo editorInfo,
+ IRemoteAccessibilityInputConnection connection, int sessionId) {
+ final AccessibilityInputMethodSession session = mSessionRef.get();
+ if (session != null) {
+ session.invalidateInput(editorInfo, connection, sessionId);
+ }
+ }
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityService.java b/android-34/android/accessibilityservice/AccessibilityService.java
new file mode 100644
index 0000000..3d4b6bf
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,3513 @@
+/*
+ * 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.accessibilityservice;
+
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
+import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
+import android.annotation.CheckResult;
+import android.annotation.ColorInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.view.accessibility.AccessibilityCache;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Accessibility services should only be used to assist users with disabilities in using
+ * Android devices and apps. They run in the background and receive callbacks by the system
+ * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
+ * in the user interface, for example, the focus has changed, a button has been clicked,
+ * etc. Such a service can optionally request the capability for querying the content
+ * of the active window. Development of an accessibility service requires extending this
+ * class and implementing its abstract methods.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating AccessibilityServices, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <h3>Lifecycle</h3>
+ * <p>
+ * The lifecycle of an accessibility service is managed exclusively by the system and
+ * follows the established service life cycle. Starting an accessibility service is triggered
+ * exclusively by the user explicitly turning the service on in device settings. After the system
+ * binds to a service, it calls {@link AccessibilityService#onServiceConnected()}. This method can
+ * be overridden by clients that want to perform post binding setup.
+ * </p>
+ * <p>
+ * An accessibility service stops either when the user turns it off in device settings or when
+ * it calls {@link AccessibilityService#disableSelf()}.
+ * </p>
+ * <h3>Declaration</h3>
+ * <p>
+ * An accessibility is declared as any other service in an AndroidManifest.xml, but it
+ * must do two things:
+ * <ul>
+ * <li>
+ * Specify that it handles the "android.accessibilityservice.AccessibilityService"
+ * {@link android.content.Intent}.
+ * </li>
+ * <li>
+ * Request the {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to
+ * ensure that only the system can bind to it.
+ * </li>
+ * </ul>
+ * If either of these items is missing, the system will ignore the accessibility service.
+ * Following is an example declaration:
+ * </p>
+ * <pre> <service android:name=".MyAccessibilityService"
+ * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.accessibilityservice.AccessibilityService" />
+ * </intent-filter>
+ * . . .
+ * </service></pre>
+ * <h3>Configuration</h3>
+ * <p>
+ * An accessibility service can be configured to receive specific types of accessibility events,
+ * listen only to specific packages, get events from each type only once in a given time frame,
+ * retrieve window content, specify a settings activity, etc.
+ * </p>
+ * <p>
+ * There are two approaches for configuring an accessibility service:
+ * </p>
+ * <ul>
+ * <li>
+ * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
+ * the service. A service declaration with a meta-data tag is presented below:
+ * <pre> <service android:name=".MyAccessibilityService">
+ * <intent-filter>
+ * <action android:name="android.accessibilityservice.AccessibilityService" />
+ * </intent-filter>
+ * <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
+ * </service></pre>
+ * <p class="note">
+ * <strong>Note:</strong> This approach enables setting all properties.
+ * </p>
+ * <p>
+ * For more details refer to {@link #SERVICE_META_DATA} and
+ * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code>.
+ * </p>
+ * </li>
+ * <li>
+ * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note
+ * that this method can be called any time to dynamically change the service configuration.
+ * <p class="note">
+ * <strong>Note:</strong> This approach enables setting only dynamically configurable properties:
+ * {@link AccessibilityServiceInfo#eventTypes},
+ * {@link AccessibilityServiceInfo#feedbackType},
+ * {@link AccessibilityServiceInfo#flags},
+ * {@link AccessibilityServiceInfo#notificationTimeout},
+ * {@link AccessibilityServiceInfo#packageNames}
+ * </p>
+ * <p>
+ * For more details refer to {@link AccessibilityServiceInfo}.
+ * </p>
+ * </li>
+ * </ul>
+ * <h3>Retrieving window content</h3>
+ * <p>
+ * A service can specify in its declaration that it can retrieve window
+ * content which is represented as a tree of {@link AccessibilityWindowInfo} and
+ * {@link AccessibilityNodeInfo} objects. Note that
+ * declaring this capability requires that the service declares its configuration via
+ * an XML resource referenced by {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Window content may be retrieved with
+ * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()},
+ * {@link AccessibilityService#findFocus(int)},
+ * {@link AccessibilityService#getWindows()}, or
+ * {@link AccessibilityService#getRootInActiveWindow()}.
+ * </p>
+ * <p class="note">
+ * <strong>Note</strong> An accessibility service may have requested to be notified for
+ * a subset of the event types, and thus be unaware when the node hierarchy has changed. It is also
+ * possible for a node to contain outdated information because the window content may change at any
+ * time.
+ * </p>
+ * <h3>Drawing Accessibility Overlays</h3>
+ * <p>Accessibility services can draw overlays on top of existing screen contents.
+ * Accessibility overlays can be used to visually highlight items on the screen
+ * e.g. indicate the current item with accessibility focus.
+ * Overlays can also offer the user a way to interact with the service directly and quickly
+ * customize the service's behavior.</p>
+ * <p>Accessibility overlays can be attached to a particular window or to the display itself.
+ * Attaching an overlay to a window allows the overly to move, grow and shrink as the window does.
+ * The overlay will maintain the same relative position within the window bounds as the window
+ * moves. The overlay will also maintain the same relative position within the window bounds if
+ * the window is resized.
+ * To attach an overlay to a window, use {@link #attachAccessibilityOverlayToWindow}.
+ * Attaching an overlay to the display means that the overlay is independent of the active
+ * windows on that display.
+ * To attach an overlay to a display, use {@link #attachAccessibilityOverlayToDisplay}. </p>
+ * <p> When positioning an overlay that is attached to a window, the service must use window
+ * coordinates. In order to position an overlay on top of an existing UI element it is necessary
+ * to know the bounds of that element in window coordinates. To find the bounds in window
+ * coordinates of an element, find the corresponding {@link AccessibilityNodeInfo} as discussed
+ * above and call {@link AccessibilityNodeInfo#getBoundsInWindow}. </p>
+ * <h3>Notification strategy</h3>
+ * <p>
+ * All accessibility services are notified of all events they have requested, regardless of their
+ * feedback type.
+ * </p>
+ * <p class="note">
+ * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
+ * events to the client too frequently since this is accomplished via an expensive
+ * interprocess call. One can think of the timeout as a criteria to determine when
+ * event generation has settled down.</p>
+ * <h3>Event types</h3>
+ * <ul>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li>
+ * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li>
+ * </ul>
+ * <h3>Feedback types</h3>
+ * <ul>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_SPOKEN}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li>
+ * </ul>
+ * @see AccessibilityEvent
+ * @see AccessibilityServiceInfo
+ * @see android.view.accessibility.AccessibilityManager
+ */
+public abstract class AccessibilityService extends Service {
+
+ /**
+ * The user has performed a touch-exploration gesture on the touch screen without ever
+ * triggering gesture detection. This gesture is only dispatched when {@link
+ * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+ *
+ * @hide
+ */
+ public static final int GESTURE_TOUCH_EXPLORATION = -2;
+
+ /**
+ * The user has performed a passthrough gesture on the touch screen without ever triggering
+ * gesture detection. This gesture is only dispatched when {@link
+ * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+ * @hide
+ */
+ public static final int GESTURE_PASSTHROUGH = -1;
+
+ /**
+ * The user has performed an unrecognized gesture on the touch screen. This gesture is only
+ * dispatched when {@link AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+ */
+ public static final int GESTURE_UNKNOWN = 0;
+
+ /**
+ * The user has performed a swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP = 1;
+
+ /**
+ * The user has performed a swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN = 2;
+
+ /**
+ * The user has performed a swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT = 3;
+
+ /**
+ * The user has performed a swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT = 4;
+
+ /**
+ * The user has performed a swipe left and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
+
+ /**
+ * The user has performed a swipe right and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
+
+ /**
+ * The user has performed a swipe up and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
+
+ /**
+ * The user has performed a swipe down and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
+
+ /**
+ * The user has performed a left and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_UP = 9;
+
+ /**
+ * The user has performed a left and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10;
+
+ /**
+ * The user has performed a right and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11;
+
+ /**
+ * The user has performed a right and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12;
+
+ /**
+ * The user has performed an up and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_LEFT = 13;
+
+ /**
+ * The user has performed an up and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14;
+
+ /**
+ * The user has performed an down and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
+
+ /**
+ * The user has performed an down and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
+
+ /**
+ * The user has performed a double tap gesture on the touch screen.
+ */
+ public static final int GESTURE_DOUBLE_TAP = 17;
+
+ /**
+ * The user has performed a double tap and hold gesture on the touch screen.
+ */
+ public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+ /**
+ * The user has performed a two-finger single tap gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SINGLE_TAP = 19;
+
+ /**
+ * The user has performed a two-finger double tap gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20;
+
+ /**
+ * The user has performed a two-finger triple tap gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21;
+
+ /**
+ * The user has performed a three-finger single tap gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SINGLE_TAP = 22;
+
+ /**
+ * The user has performed a three-finger double tap gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23;
+
+ /**
+ * The user has performed a three-finger triple tap gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24;
+
+ /**
+ * The user has performed a two-finger swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_UP = 25;
+
+ /**
+ * The user has performed a two-finger swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26;
+
+ /**
+ * The user has performed a two-finger swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27;
+
+ /**
+ * The user has performed a two-finger swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28;
+
+ /**
+ * The user has performed a three-finger swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_UP = 29;
+
+ /**
+ * The user has performed a three-finger swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30;
+
+ /**
+ * The user has performed a three-finger swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31;
+
+ /**
+ * The user has performed a three-finger swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32;
+
+ /** The user has performed a four-finger swipe up gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_UP = 33;
+
+ /** The user has performed a four-finger swipe down gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34;
+
+ /** The user has performed a four-finger swipe left gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35;
+
+ /** The user has performed a four-finger swipe right gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36;
+
+ /** The user has performed a four-finger single tap gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_SINGLE_TAP = 37;
+
+ /** The user has performed a four-finger double tap gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38;
+
+ /** The user has performed a four-finger triple tap gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
+
+ /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40;
+
+ /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+
+ /** The user has performed a two-finger triple-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43;
+
+ /** The user has performed a three-finger single-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
+
+ /** The user has performed a three-finger triple-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45;
+
+ /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+ public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.accessibilityservice.AccessibilityService";
+
+ /**
+ * Name under which an AccessibilityService component publishes information
+ * about itself. This meta-data must reference an XML resource containing an
+ * <code><{@link android.R.styleable#AccessibilityService accessibility-service}></code>
+ * tag. This is a sample XML file configuring an accessibility service:
+ * <pre> <accessibility-service
+ * android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
+ * android:packageNames="foo.bar, foo.baz"
+ * android:accessibilityFeedbackType="feedbackSpoken"
+ * android:notificationTimeout="100"
+ * android:accessibilityFlags="flagDefault"
+ * android:settingsActivity="foo.bar.TestBackActivity"
+ * android:canRetrieveWindowContent="true"
+ * android:canRequestTouchExplorationMode="true"
+ * . . .
+ * /></pre>
+ */
+ public static final String SERVICE_META_DATA = "android.accessibilityservice";
+
+ /**
+ * Action to go back.
+ */
+ public static final int GLOBAL_ACTION_BACK = 1;
+
+ /**
+ * Action to go home.
+ */
+ public static final int GLOBAL_ACTION_HOME = 2;
+
+ /**
+ * Action to toggle showing the overview of recent apps. Will fail on platforms that don't
+ * show recent apps.
+ */
+ public static final int GLOBAL_ACTION_RECENTS = 3;
+
+ /**
+ * Action to open the notifications.
+ */
+ public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
+
+ /**
+ * Action to open the quick settings.
+ */
+ public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
+
+ /**
+ * Action to open the power long-press dialog.
+ */
+ public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
+
+ /**
+ * Action to toggle docking the current app's window.
+ * <p>
+ * <strong>Note:</strong> It is effective only if it appears in {@link #getSystemActions()}.
+ */
+ public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
+
+ /**
+ * Action to lock the screen
+ */
+ public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
+ /**
+ * Action to take a screenshot
+ */
+ public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
+
+ /**
+ * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+ * play/stop media
+ */
+ public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
+
+ /**
+ * Action to trigger the Accessibility Button
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11;
+
+ /**
+ * Action to bring up the Accessibility Button's chooser menu
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12;
+
+ /**
+ * Action to trigger the Accessibility Shortcut. This shortcut has a hardware trigger and can
+ * be activated by holding down the two volume keys.
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13;
+
+ /**
+ * Action to show Launcher's all apps.
+ */
+ public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14;
+
+ /**
+ * Action to dismiss the notification shade
+ */
+ public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+
+ /**
+ * Action to trigger dpad up keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_UP = 16;
+
+ /**
+ * Action to trigger dpad down keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_DOWN = 17;
+
+ /**
+ * Action to trigger dpad left keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_LEFT = 18;
+
+ /**
+ * Action to trigger dpad right keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_RIGHT = 19;
+
+ /**
+ * Action to trigger dpad center keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_CENTER = 20;
+
+ private static final String LOG_TAG = "AccessibilityService";
+
+ /**
+ * Interface used by IAccessibilityServiceClientWrapper to call the service from its main
+ * thread.
+ * @hide
+ */
+ public interface Callbacks {
+ void onAccessibilityEvent(AccessibilityEvent event);
+ void onInterrupt();
+ void onServiceConnected();
+ void init(int connectionId, IBinder windowToken);
+ /** The detected gesture information for different displays */
+ boolean onGesture(AccessibilityGestureEvent gestureInfo);
+ boolean onKeyEvent(KeyEvent event);
+ /** Magnification changed callbacks for different displays */
+ void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config);
+ /** Callbacks for receiving motion events. */
+ void onMotionEvent(MotionEvent event);
+ /** Callback for tuch state changes. */
+ void onTouchStateChanged(int displayId, int state);
+ void onSoftKeyboardShowModeChanged(int showMode);
+ void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+ void onFingerprintCapturingGesturesChanged(boolean active);
+ void onFingerprintGesture(int gesture);
+ /** Accessbility button clicked callbacks for different displays */
+ void onAccessibilityButtonClicked(int displayId);
+ void onAccessibilityButtonAvailabilityChanged(boolean available);
+ /** This is called when the system action list is changed. */
+ void onSystemActionsChanged();
+ /** This is called when an app requests ime sessions or when the service is enabled. */
+ void createImeSession(IAccessibilityInputMethodSessionCallback callback);
+ /** This is called when an app starts input or when the service is enabled. */
+ void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, boolean restarting);
+ }
+
+ /**
+ * Annotations for Soft Keyboard show modes so tools can catch invalid show modes.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SHOW_MODE_" }, value = {
+ SHOW_MODE_AUTO,
+ SHOW_MODE_HIDDEN,
+ SHOW_MODE_IGNORE_HARD_KEYBOARD
+ })
+ public @interface SoftKeyboardShowMode {}
+
+ /**
+ * Allow the system to control when the soft keyboard is shown.
+ * @see SoftKeyboardController
+ */
+ public static final int SHOW_MODE_AUTO = 0;
+
+ /**
+ * Never show the soft keyboard.
+ * @see SoftKeyboardController
+ */
+ public static final int SHOW_MODE_HIDDEN = 1;
+
+ /**
+ * Allow the soft keyboard to be shown, even if a hard keyboard is connected
+ * @see SoftKeyboardController
+ */
+ public static final int SHOW_MODE_IGNORE_HARD_KEYBOARD = 2;
+
+ /**
+ * Mask used to cover the show modes supported in public API
+ * @hide
+ */
+ public static final int SHOW_MODE_MASK = 0x03;
+
+ /**
+ * Bit used to hold the old value of the hard IME setting to restore when a service is shut
+ * down.
+ * @hide
+ */
+ public static final int SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE = 0x20000000;
+
+ /**
+ * Bit for show mode setting to indicate that the user has overridden the hard keyboard
+ * behavior.
+ * @hide
+ */
+ public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000;
+
+ /**
+ * Annotations for error codes of taking screenshot.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TAKE_SCREENSHOT_" }, value = {
+ ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+ ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
+ ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+ ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
+ })
+ public @interface ScreenshotErrorCode {}
+
+ /**
+ * The status of taking screenshot is success.
+ * @hide
+ */
+ public static final int TAKE_SCREENSHOT_SUCCESS = 0;
+
+ /**
+ * The status of taking screenshot is failure and the reason is internal error.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1;
+
+ /**
+ * The status of taking screenshot is failure and the reason is no accessibility access.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2;
+
+ /**
+ * The status of taking screenshot is failure and the reason is that too little time has
+ * elapsed since the last screenshot.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3;
+
+ /**
+ * The status of taking screenshot is failure and the reason is invalid display Id.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
+
+ /**
+ * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+ /**
+ * The status of taking screenshot is failure and the reason is the window contains secure
+ * content.
+ * @see WindowManager.LayoutParams#FLAG_SECURE
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+ /**
+ * The interval time of calling
+ * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
+ * @hide
+ */
+ @TestApi
+ public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 333;
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_STATUS =
+ "screenshot_status";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
+ "screenshot_hardwareBuffer";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
+ "screenshot_colorSpace";
+
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
+ "screenshot_timestamp";
+
+ private int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+ @UnsupportedAppUsage
+ private AccessibilityServiceInfo mInfo;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private IBinder mWindowToken;
+
+ private WindowManager mWindowManager;
+
+ /** List of magnification controllers, mapping from displayId -> MagnificationController. */
+ private final SparseArray<MagnificationController> mMagnificationControllers =
+ new SparseArray<>(0);
+ /**
+ * List of touch interaction controllers, mapping from displayId -> TouchInteractionController.
+ */
+ private final SparseArray<TouchInteractionController> mTouchInteractionControllers =
+ new SparseArray<>(0);
+
+ private SoftKeyboardController mSoftKeyboardController;
+ private InputMethod mInputMethod;
+ private boolean mInputMethodInitialized = false;
+ private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
+ new SparseArray<>(0);
+
+ private int mGestureStatusCallbackSequence;
+
+ private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos;
+
+ private final Object mLock = new Object();
+
+ private FingerprintGestureController mFingerprintGestureController;
+
+ private int mMotionEventSources;
+
+ /**
+ * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
+ *
+ * @param event The new event. This event is owned by the caller and cannot be used after
+ * this method returns. Services wishing to use the event after this method returns should
+ * make a copy.
+ */
+ public abstract void onAccessibilityEvent(AccessibilityEvent event);
+
+ /**
+ * Callback for interrupting the accessibility feedback.
+ */
+ public abstract void onInterrupt();
+
+ /**
+ * Dispatches service connection to internal components first, then the
+ * client code.
+ */
+ private void dispatchServiceConnected() {
+ synchronized (mLock) {
+ for (int i = 0; i < mMagnificationControllers.size(); i++) {
+ mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
+ }
+ final AccessibilityServiceInfo info = getServiceInfo();
+ if (info != null) {
+ updateInputMethod(info);
+ mMotionEventSources = info.getMotionEventSources();
+ }
+ }
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.onServiceConnected();
+ }
+
+ // The client gets to handle service connection last, after we've set
+ // up any state upon which their code may rely.
+ onServiceConnected();
+ }
+
+ private void updateInputMethod(AccessibilityServiceInfo info) {
+ if (info != null) {
+ boolean requestIme = (info.flags
+ & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
+ if (requestIme && !mInputMethodInitialized) {
+ mInputMethod = onCreateInputMethod();
+ mInputMethodInitialized = true;
+ } else if (!requestIme & mInputMethodInitialized) {
+ mInputMethod = null;
+ mInputMethodInitialized = false;
+ }
+ }
+ }
+
+ /**
+ * This method is a part of the {@link AccessibilityService} lifecycle and is
+ * called after the system has successfully bound to the service. If is
+ * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
+ *
+ * @see AccessibilityServiceInfo
+ * @see #setServiceInfo(AccessibilityServiceInfo)
+ */
+ protected void onServiceConnected() {
+
+ }
+
+ /**
+ * Called by {@link #onGesture(AccessibilityGestureEvent)} when the user performs a specific
+ * gesture on the default display.
+ *
+ * <strong>Note:</strong> To receive gestures an accessibility service must
+ * request that the device is in touch exploration mode by setting the
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag.
+ *
+ * @param gestureId The unique id of the performed gesture.
+ *
+ * @return Whether the gesture was handled.
+ * @deprecated Override {@link #onGesture(AccessibilityGestureEvent)} instead.
+ *
+ * @see #GESTURE_SWIPE_UP
+ * @see #GESTURE_SWIPE_UP_AND_LEFT
+ * @see #GESTURE_SWIPE_UP_AND_DOWN
+ * @see #GESTURE_SWIPE_UP_AND_RIGHT
+ * @see #GESTURE_SWIPE_DOWN
+ * @see #GESTURE_SWIPE_DOWN_AND_LEFT
+ * @see #GESTURE_SWIPE_DOWN_AND_UP
+ * @see #GESTURE_SWIPE_DOWN_AND_RIGHT
+ * @see #GESTURE_SWIPE_LEFT
+ * @see #GESTURE_SWIPE_LEFT_AND_UP
+ * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
+ * @see #GESTURE_SWIPE_LEFT_AND_DOWN
+ * @see #GESTURE_SWIPE_RIGHT
+ * @see #GESTURE_SWIPE_RIGHT_AND_UP
+ * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
+ * @see #GESTURE_SWIPE_RIGHT_AND_DOWN
+ */
+ @Deprecated
+ protected boolean onGesture(int gestureId) {
+ return false;
+ }
+
+ /**
+ * Called by the system when the user performs a specific gesture on the
+ * specific touch screen.
+ *<p>
+ * <strong>Note:</strong> To receive gestures an accessibility service must
+ * request that the device is in touch exploration mode by setting the
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag.
+ *<p>
+ * <strong>Note:</strong> The default implementation calls {@link #onGesture(int)} when the
+ * touch screen is default display.
+ *
+ * @param gestureEvent The information of gesture.
+ *
+ * @return Whether the gesture was handled.
+ *
+ */
+ public boolean onGesture(@NonNull AccessibilityGestureEvent gestureEvent) {
+ if (gestureEvent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ onGesture(gestureEvent.getGestureId());
+ }
+ return false;
+ }
+
+ /**
+ * Callback that allows an accessibility service to observe the key events
+ * before they are passed to the rest of the system. This means that the events
+ * are first delivered here before they are passed to the device policy, the
+ * input method, or applications.
+ * <p>
+ * <strong>Note:</strong> It is important that key events are handled in such
+ * a way that the event stream that would be passed to the rest of the system
+ * is well-formed. For example, handling the down event but not the up event
+ * and vice versa would generate an inconsistent event stream.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The key events delivered in this method are copies
+ * and modifying them will have no effect on the events that will be passed
+ * to the system. This method is intended to perform purely filtering
+ * functionality.
+ * <p>
+ *
+ * @param event The event to be processed. This event is owned by the caller and cannot be used
+ * after this method returns. Services wishing to use the event after this method returns should
+ * make a copy.
+ * @return If true then the event will be consumed and not delivered to
+ * applications, otherwise it will be delivered as usual.
+ */
+ protected boolean onKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Callback that allows an accessibility service to observe generic {@link MotionEvent}s.
+ * <p>
+ * Prefer {@link TouchInteractionController} to observe and control touchscreen events,
+ * including touch gestures. If this or any enabled service is using
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} then
+ * {@link #onMotionEvent} will not receive touchscreen events.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The service must first request to listen to events using
+ * {@link AccessibilityServiceInfo#setMotionEventSources}.
+ * {@link MotionEvent}s from sources in {@link AccessibilityServiceInfo#getMotionEventSources()}
+ * are not sent to the rest of the system. To stop listening to events from a given source, call
+ * {@link AccessibilityServiceInfo#setMotionEventSources} with that source removed.
+ * </p>
+ * @param event The event to be processed.
+ */
+ public void onMotionEvent(@NonNull MotionEvent event) { }
+
+ /**
+ * Gets the windows on the screen of the default display. This method returns only the windows
+ * that a sighted user can interact with, as opposed to all windows.
+ * For example, if there is a modal dialog shown and the user cannot touch
+ * anything behind it, then only the modal window will be reported
+ * (assuming it is the top one). For convenience the returned windows
+ * are ordered in a descending layer order, which is the windows that
+ * are on top are reported first. Since the user can always
+ * interact with the window that has input focus by typing, the focused
+ * window is always returned (even if covered by a modal window).
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag.
+ * </p>
+ *
+ * @return The windows if there are windows and the service is can retrieve
+ * them, otherwise an empty list.
+ */
+ public List<AccessibilityWindowInfo> getWindows() {
+ return AccessibilityInteractionClient.getInstance(this).getWindows(mConnectionId);
+ }
+
+ /**
+ * Gets the windows on the screen of all displays. This method returns only the windows
+ * that a sighted user can interact with, as opposed to all windows.
+ * For example, if there is a modal dialog shown and the user cannot touch
+ * anything behind it, then only the modal window will be reported
+ * (assuming it is the top one). For convenience the returned windows
+ * are ordered in a descending layer order, which is the windows that
+ * are on top are reported first. Since the user can always
+ * interact with the window that has input focus by typing, the focused
+ * window is always returned (even if covered by a modal window).
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag.
+ * </p>
+ *
+ * @return The windows of all displays if there are windows and the service is can retrieve
+ * them, otherwise an empty list. The key of SparseArray is display ID.
+ */
+ @NonNull
+ public final SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
+ return AccessibilityInteractionClient.getInstance(this).getWindowsOnAllDisplays(
+ mConnectionId);
+ }
+
+ /**
+ * Gets the root node in the currently active window if this service
+ * can retrieve window content. The active window is the one that the user
+ * is currently touching or the window with input focus, if the user is not
+ * touching any window. It could be from any logical display.
+ * <p>
+ * <strong>Note:</strong> In order to access the root node your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ *
+ * @return The root node if this service can retrieve window content.
+ * @see AccessibilityWindowInfo#isActive() for more explanation about the active window.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+ }
+
+ /**
+ * Gets the root node in the currently active window if this service
+ * can retrieve window content. The active window is the one that the user
+ * is currently touching or the window with input focus, if the user is not
+ * touching any window. It could be from any logical display.
+ *
+ * @param prefetchingStrategy the prefetching strategy.
+ * @return The root node if this service can retrieve window content.
+ *
+ * @see #getRootInActiveWindow()
+ * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
+ */
+ @Nullable
+ public AccessibilityNodeInfo getRootInActiveWindow(
+ @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
+ return AccessibilityInteractionClient.getInstance(this).getRootInActiveWindow(
+ mConnectionId, prefetchingStrategy);
+ }
+
+ /**
+ * Disables the service. After calling this method, the service will be disabled and settings
+ * will show that it is turned off.
+ */
+ public final void disableSelf() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.disableSelf();
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public Context createDisplayContext(Display display) {
+ return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(int type, @Nullable Bundle options) {
+ final Context context = super.createWindowContext(type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(@NonNull Display display, int type,
+ @Nullable Bundle options) {
+ final Context context = super.createWindowContext(display, type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ /**
+ * Returns the magnification controller, which may be used to query and
+ * modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @return the magnification controller
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController() {
+ return getMagnificationController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the magnification controller of specified logical display, which may be used to
+ * query and modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return the magnification controller
+ *
+ * @hide
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController(int displayId) {
+ synchronized (mLock) {
+ MagnificationController controller = mMagnificationControllers.get(displayId);
+ if (controller == null) {
+ controller = new MagnificationController(this, mLock, displayId);
+ mMagnificationControllers.put(displayId, controller);
+ }
+ return controller;
+ }
+ }
+
+ /**
+ * Get the controller for fingerprint gestures. This feature requires {@link
+ * AccessibilityServiceInfo#CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES}.
+ *
+ *<strong>Note: </strong> The service must be connected before this method is called.
+ *
+ * @return The controller for fingerprint gestures, or {@code null} if gestures are unavailable.
+ */
+ @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
+ public final @NonNull FingerprintGestureController getFingerprintGestureController() {
+ if (mFingerprintGestureController == null) {
+ mFingerprintGestureController = new FingerprintGestureController(
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId));
+ }
+ return mFingerprintGestureController;
+ }
+
+ /**
+ * Dispatch a gesture to the touch screen. Any gestures currently in progress, whether from
+ * the user, this service, or another service, will be cancelled.
+ * <p>
+ * The gesture will be dispatched as if it were performed directly on the screen by a user, so
+ * the events may be affected by features such as magnification and explore by touch.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> In order to dispatch gestures, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ * </p>
+ *
+ * @param gesture The gesture to dispatch
+ * @param callback The object to call back when the status of the gesture is known. If
+ * {@code null}, no status is reported.
+ * @param handler The handler on which to call back the {@code callback} object. If
+ * {@code null}, the object is called back on the service's main thread.
+ *
+ * @return {@code true} if the gesture is dispatched, {@code false} if not.
+ */
+ public final boolean dispatchGesture(@NonNull GestureDescription gesture,
+ @Nullable GestureResultCallback callback,
+ @Nullable Handler handler) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ int sampleTimeMs = calculateGestureSampleTimeMs(gesture.getDisplayId());
+ List<GestureDescription.GestureStep> steps =
+ MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, sampleTimeMs);
+ try {
+ synchronized (mLock) {
+ mGestureStatusCallbackSequence++;
+ if (callback != null) {
+ if (mGestureStatusCallbackInfos == null) {
+ mGestureStatusCallbackInfos = new SparseArray<>();
+ }
+ GestureResultCallbackInfo callbackInfo = new GestureResultCallbackInfo(gesture,
+ callback, handler);
+ mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
+ }
+ connection.dispatchGesture(mGestureStatusCallbackSequence,
+ new ParceledListSlice<>(steps), gesture.getDisplayId());
+ }
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the sample time in millis of gesture steps for the current display.
+ *
+ * <p>For gestures to be smooth they should line up with the refresh rate of the display.
+ * On versions of Android before R, the sample time was fixed to 100ms.
+ */
+ private int calculateGestureSampleTimeMs(int displayId) {
+ if (getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.Q) {
+ return 100;
+ }
+ Display display = getSystemService(DisplayManager.class).getDisplay(
+ displayId);
+ if (display == null) {
+ return 100;
+ }
+ int msPerSecond = 1000;
+ int sampleTimeMs = (int) (msPerSecond / display.getRefreshRate());
+ if (sampleTimeMs < 1) {
+ // Should be impossible, but do not return 0.
+ return 100;
+ }
+ return sampleTimeMs;
+ }
+
+ void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
+ if (mGestureStatusCallbackInfos == null) {
+ return;
+ }
+ GestureResultCallbackInfo callbackInfo;
+ synchronized (mLock) {
+ callbackInfo = mGestureStatusCallbackInfos.get(sequence);
+ mGestureStatusCallbackInfos.remove(sequence);
+ }
+ final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
+ if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
+ && (callbackInfo.callback != null)) {
+ if (callbackInfo.handler != null) {
+ callbackInfo.handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (completedSuccessfully) {
+ finalCallbackInfo.callback
+ .onCompleted(finalCallbackInfo.gestureDescription);
+ } else {
+ finalCallbackInfo.callback
+ .onCancelled(finalCallbackInfo.gestureDescription);
+ }
+ }
+ });
+ return;
+ }
+ if (completedSuccessfully) {
+ callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
+ } else {
+ callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
+ }
+ }
+ }
+
+ private void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ MagnificationController controller;
+ synchronized (mLock) {
+ controller = mMagnificationControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.dispatchMagnificationChanged(region, config);
+ }
+ }
+
+ /**
+ * Callback for fingerprint gesture handling
+ * @param active If gesture detection is active
+ */
+ private void onFingerprintCapturingGesturesChanged(boolean active) {
+ getFingerprintGestureController().onGestureDetectionActiveChanged(active);
+ }
+
+ /**
+ * Callback for fingerprint gesture handling
+ * @param gesture The identifier for the gesture performed
+ */
+ private void onFingerprintGesture(int gesture) {
+ getFingerprintGestureController().onGesture(gesture);
+ }
+
+ int getConnectionId() {
+ return mConnectionId;
+ }
+
+ /**
+ * Used to control and query the state of display magnification.
+ */
+ public static final class MagnificationController {
+ private final AccessibilityService mService;
+ private final int mDisplayId;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the
+ * first magnification listener.
+ */
+ private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+ private final Object mLock;
+
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
+ int displayId) {
+ mService = service;
+ mLock = lock;
+ mDisplayId = displayId;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnectedLocked() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the service's main
+ * thread.
+ *
+ * @param listener the listener to add, must be non-{@code null}
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the specified
+ * {@link Handler}'s thread, or on the service's main thread if the
+ * handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which the callback should execute, or
+ * {@code null} to execute on the service's main thread
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we
+ // still have listeners when it connects then we can try again.
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified change listener from the list of magnification change listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if the listener was removed, {@code false} otherwise
+ */
+ public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need
+ // callbacks from the service anymore.
+ setMagnificationCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+ }
+
+ private void setMagnificationCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches magnification changes to any registered listeners. This
+ * should be called on the service's main thread.
+ */
+ void dispatchMagnificationChanged(final @NonNull Region region,
+ final MagnificationConfig config) {
+ final ArrayMap<OnMagnificationChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received magnification changed "
+ + "callback with no listeners registered!");
+ setMagnificationCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnMagnificationChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> {
+ listener.onMagnificationChanged(MagnificationController.this,
+ region, config);
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onMagnificationChanged(this, region, config);
+ }
+ }
+ }
+
+ /**
+ * Gets the {@link MagnificationConfig} of the controlling magnifier on the display.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return null.
+ * </p>
+ *
+ * @return the magnification config that the service controls
+ */
+ public @Nullable MagnificationConfig getMagnificationConfig() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationConfig(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain magnification config", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the current magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 1.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the scale of full-screen
+ * magnification. To get the scale of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
+ *
+ * @return the current magnification scale
+ * @deprecated Use {@link #getMagnificationConfig()} instead
+ */
+ @Deprecated
+ public float getScale() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationScale(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain scale", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return 1.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative X coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the center position of full-screen
+ * magnification. To get the magnification center of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
+ *
+ * @return the unscaled screen-relative X coordinate of the center of
+ * the magnified region
+ * @deprecated Use {@link #getMagnificationConfig()} instead
+ */
+ @Deprecated
+ public float getCenterX() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterX(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center X", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative Y coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the center position of full-screen
+ * magnification. To get the magnification center of the current controlling magnifier,
+ * use {@link #getMagnificationConfig} instead.
+ * </p>
+ *
+ * @return the unscaled screen-relative Y coordinate of the center of
+ * the magnified region
+ * @deprecated Use {@link #getMagnificationConfig()} instead
+ */
+ @Deprecated
+ public float getCenterY() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterY(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center Y", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the region of the screen currently active for magnification. Changes to
+ * magnification scale and center only affect this portion of the screen. The rest of the
+ * screen, for example input methods, cannot be magnified. This region is relative to the
+ * unscaled screen and is independent of the scale and center point.
+ * <p>
+ * The returned region will be empty if magnification is not active. Magnification is active
+ * if magnification gestures are enabled or if a service is running that can control
+ * magnification.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return an empty region.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API gets the magnification region of full-screen
+ * magnification. To get the magnification region of the current controlling magnifier,
+ * use {@link #getCurrentMagnificationRegion()} instead.
+ * </p>
+ *
+ * @return the region of the screen currently active for magnification, or an empty region
+ * if magnification is not active.
+ * @deprecated Use {@link #getCurrentMagnificationRegion()} instead
+ */
+ @Deprecated
+ @NonNull
+ public Region getMagnificationRegion() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationRegion(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain magnified region", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Region.obtain();
+ }
+
+ /**
+ * Returns the region of the screen currently active for magnification if the
+ * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}.
+ * Returns the region of screen projected on the magnification window if the
+ * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}.
+ *
+ * <p>
+ * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+ * the returned region will be empty if the magnification is
+ * not active. And the magnification is active if magnification gestures are enabled
+ * or if a service is running that can control magnification.
+ * </p><p>
+ * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+ * the returned region will be empty if the magnification is not activated.
+ * </p><p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return an empty region.
+ * </p>
+ *
+ * @return the magnification region of the currently controlling magnification
+ */
+ @NonNull
+ public Region getCurrentMagnificationRegion() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getCurrentMagnificationRegion(mDisplayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain the current magnified region", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Region.obtain();
+ }
+
+ /**
+ * Resets magnification scale and center to their default (e.g. no
+ * magnification) values.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * <p>
+ * <strong>Note:</strong> This legacy API reset full-screen magnification.
+ * To reset the current controlling magnifier, use
+ * {@link #resetCurrentMagnification(boolean)} ()} instead.
+ * </p>
+ *
+ * @param animate {@code true} to animate from the current scale and
+ * center or {@code false} to reset the scale and center
+ * immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean reset(boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.resetMagnification(mDisplayId, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to reset", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Resets magnification scale and center of the controlling magnification
+ * to their default (e.g. no magnification) values.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * </p>
+ *
+ * @param animate {@code true} to animate from the current scale and
+ * center or {@code false} to reset the scale and center
+ * immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean resetCurrentMagnification(boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.resetCurrentMagnification(mDisplayId, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to reset", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the {@link MagnificationConfig}. The service controls the magnification by
+ * setting the config.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * </p>
+ *
+ * @param config the magnification config
+ * @param animate {@code true} to animate from the current spec or
+ * {@code false} to set the spec immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean setMagnificationConfig(@NonNull MagnificationConfig config,
+ boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setMagnificationConfig(mDisplayId, config, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set magnification config", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * <p>
+ * <strong>Note:</strong> This legacy API sets the scale of full-screen
+ * magnification. To set the scale of the specified magnifier,
+ * use {@link #setMagnificationConfig} instead.
+ * </p>
+ *
+ * @param scale the magnification scale to set, must be >= 1 and <= 8
+ * @param animate {@code true} to animate from the current scale or
+ * {@code false} to set the scale immediately
+ * @return {@code true} on success, {@code false} on failure
+ * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
+ */
+ @Deprecated
+ public boolean setScale(float scale, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setScale(scale).build();
+ return connection.setMagnificationConfig(mDisplayId, config, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set scale", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the center of the magnified viewport.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This legacy API sets the center of full-screen
+ * magnification. To set the center of the specified magnifier,
+ * use {@link #setMagnificationConfig} instead.
+ * </p>
+ *
+ * @param centerX the unscaled screen-relative X coordinate on which to
+ * center the viewport
+ * @param centerY the unscaled screen-relative Y coordinate on which to
+ * center the viewport
+ * @param animate {@code true} to animate from the current viewport
+ * center or {@code false} to set the center immediately
+ * @return {@code true} on success, {@code false} on failure
+ * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
+ */
+ @Deprecated
+ public boolean setCenter(float centerX, float centerY, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setCenterX(centerX).setCenterY(centerY).build();
+ return connection.setMagnificationConfig(mDisplayId, config, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set center", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the state of magnification.
+ */
+ public interface OnMagnificationChangedListener {
+ /**
+ * Called when the magnified region, scale, or center changes.
+ * <p>
+ * <strong>Note:</strong> This legacy callback notifies only full-screen
+ * magnification change.
+ * </p>
+ *
+ * @param controller the magnification controller
+ * @param region the magnification region
+ * @param scale the new scale
+ * @param centerX the new X coordinate, in unscaled coordinates, around which
+ * magnification is focused
+ * @param centerY the new Y coordinate, in unscaled coordinates, around which
+ * magnification is focused
+ * @deprecated Override
+ * {@link #onMagnificationChanged(MagnificationController, Region, MagnificationConfig)}
+ * instead
+ */
+ @Deprecated
+ void onMagnificationChanged(@NonNull MagnificationController controller,
+ @NonNull Region region, float scale, float centerX, float centerY);
+
+ /**
+ * Called when the magnified region, mode, scale, or center changes of
+ * all magnification modes.
+ * <p>
+ * <strong>Note:</strong> This method can be overridden to listen to the
+ * magnification changes of all magnification modes then the legacy callback
+ * would not receive the notifications.
+ * Skipping calling super when overriding this method results in
+ * {@link #onMagnificationChanged(MagnificationController, Region, float, float, float)}
+ * not getting called.
+ * </p>
+ *
+ * @param controller the magnification controller
+ * @param region the magnification region
+ * If the config mode is
+ * {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+ * it is the region of the screen currently active for magnification.
+ * that is the same region as {@link #getMagnificationRegion()}.
+ * If the config mode is
+ * {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+ * it is the region of screen projected on the magnification window.
+ * @param config The magnification config. That has the controlling magnification
+ * mode, the new scale and the new screen-relative center position
+ */
+ default void onMagnificationChanged(@NonNull MagnificationController controller,
+ @NonNull Region region, @NonNull MagnificationConfig config) {
+ if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
+ onMagnificationChanged(controller, region,
+ config.getScale(), config.getCenterX(), config.getCenterY());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the soft keyboard controller, which may be used to query and modify the soft keyboard
+ * show mode.
+ *
+ * @return the soft keyboard controller
+ */
+ @NonNull
+ public final SoftKeyboardController getSoftKeyboardController() {
+ synchronized (mLock) {
+ if (mSoftKeyboardController == null) {
+ mSoftKeyboardController = new SoftKeyboardController(this, mLock);
+ }
+ return mSoftKeyboardController;
+ }
+ }
+
+ /**
+ * The default implementation returns our default {@link InputMethod}. Subclasses can override
+ * it to provide their own customized version. Accessibility services need to set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs.
+ *
+ * @return the InputMethod.
+ */
+ @NonNull
+ public InputMethod onCreateInputMethod() {
+ return new InputMethod(this);
+ }
+
+ /**
+ * Returns the InputMethod instance after the system calls {@link #onCreateInputMethod()},
+ * which may be used to input text or get editable text selection change notifications. It will
+ * return null if the accessibility service doesn't set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag or the system doesn't call
+ * {@link #onCreateInputMethod()}.
+ *
+ * @return the InputMethod instance
+ */
+ @Nullable
+ public final InputMethod getInputMethod() {
+ return mInputMethod;
+ }
+
+ private void onSoftKeyboardShowModeChanged(int showMode) {
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode);
+ }
+ }
+
+ /**
+ * Used to control, query, and listen for changes to the soft keyboard show mode.
+ * <p>
+ * Accessibility services may request to override the decisions normally made about whether or
+ * not the soft keyboard is shown.
+ * <p>
+ * If multiple services make conflicting requests, the last request is honored. A service may
+ * register a listener to find out if the mode has changed under it.
+ * <p>
+ * If the user takes action to override the behavior behavior requested by an accessibility
+ * service, the user's request takes precendence, the show mode will be reset to
+ * {@link AccessibilityService#SHOW_MODE_AUTO}, and services will no longer be able to control
+ * that aspect of the soft keyboard's behavior.
+ * <p>
+ * Note: Because soft keyboards are independent apps, the framework does not have total control
+ * over their behavior. They may choose to show themselves, or not, without regard to requests
+ * made here. So the framework will make a best effort to deliver the behavior requested, but
+ * cannot guarantee success.
+ *
+ * @see AccessibilityService#SHOW_MODE_AUTO
+ * @see AccessibilityService#SHOW_MODE_HIDDEN
+ * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
+ */
+ public static final class SoftKeyboardController {
+ private final AccessibilityService mService;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the first
+ * soft keyboard change listener.
+ */
+ private ArrayMap<OnShowModeChangedListener, Handler> mListeners;
+ private final Object mLock;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ENABLE_IME_SUCCESS,
+ ENABLE_IME_FAIL_BY_ADMIN,
+ ENABLE_IME_FAIL_UNKNOWN
+ })
+ public @interface EnableImeResult {}
+ /**
+ * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action succeeded.
+ */
+ public static final int ENABLE_IME_SUCCESS = 0;
+ /**
+ * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action failed
+ * because the InputMethod is not permitted by device policy manager.
+ */
+ public static final int ENABLE_IME_FAIL_BY_ADMIN = 1;
+ /**
+ * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action failed
+ * and the reason is unknown.
+ */
+ public static final int ENABLE_IME_FAIL_UNKNOWN = 2;
+
+ SoftKeyboardController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ mService = service;
+ mLock = lock;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnected() {
+ synchronized(mLock) {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of show mode change listeners. The
+ * callback will occur on the service's main thread. Listener is not called on registration.
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
+ addOnShowModeChangedListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of soft keyboard show mode change
+ * listeners. The callback will occur on the specified {@link Handler}'s thread, or on the
+ * services's main thread if the handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which to callback should execute, or {@code null} to
+ * execute on the service's main thread
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we still have
+ // listeners when it connects, we can try again.
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified change listener from the list of keyboard show mode change
+ * listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if the listener was removed, {@code false} otherwise
+ */
+ public boolean removeOnShowModeChangedListener(
+ @NonNull OnShowModeChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need callbacks from the
+ // service anymore.
+ setSoftKeyboardCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+ }
+
+ private void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setSoftKeyboardCallbackEnabled(enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the soft keyboard show mode change to any registered listeners. This should
+ * be called on the service's main thread.
+ */
+ void dispatchSoftKeyboardShowModeChanged(final int showMode) {
+ final ArrayMap<OnShowModeChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.w(LOG_TAG, "Received soft keyboard show mode changed callback"
+ + " with no listeners registered!");
+ setSoftKeyboardCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnShowModeChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onShowModeChanged(SoftKeyboardController.this, showMode);
+ }
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onShowModeChanged(this, showMode);
+ }
+ }
+ }
+
+ /**
+ * Returns the show mode of the soft keyboard.
+ *
+ * @return the current soft keyboard show mode
+ *
+ * @see AccessibilityService#SHOW_MODE_AUTO
+ * @see AccessibilityService#SHOW_MODE_HIDDEN
+ * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
+ */
+ @SoftKeyboardShowMode
+ public int getShowMode() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getSoftKeyboardShowMode();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return SHOW_MODE_AUTO;
+ }
+
+ /**
+ * Sets the soft keyboard show mode.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will have no effect and return {@code false}.
+ *
+ * @param showMode the new show mode for the soft keyboard
+ * @return {@code true} on success
+ *
+ * @see AccessibilityService#SHOW_MODE_AUTO
+ * @see AccessibilityService#SHOW_MODE_HIDDEN
+ * @see AccessibilityService#SHOW_MODE_IGNORE_HARD_KEYBOARD
+ */
+ public boolean setShowMode(@SoftKeyboardShowMode int showMode) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setSoftKeyboardShowMode(showMode);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the soft keyboard show mode.
+ */
+ public interface OnShowModeChangedListener {
+ /**
+ * Called when the soft keyboard behavior changes. The default show mode is
+ * {@code SHOW_MODE_AUTO}, where the soft keyboard is shown when a text input field is
+ * focused. An AccessibilityService can also request the show mode
+ * {@code SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
+ *
+ * @param controller the soft keyboard controller
+ * @param showMode the current soft keyboard show mode
+ */
+ void onShowModeChanged(@NonNull SoftKeyboardController controller,
+ @SoftKeyboardShowMode int showMode);
+ }
+
+ /**
+ * Switches the current IME for the user for whom the service is enabled. The change will
+ * persist until the current IME is explicitly changed again, and may persist beyond the
+ * life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to make current. This IME must be installed and
+ * enabled.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId},
+ * {@code false} if the input method specified is not installed, not enabled, or
+ * otherwise not available to become the current IME
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ public boolean switchToInputMethod(@NonNull String imeId) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.switchToInputMethod(imeId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Enable or disable the specified IME for the user for whom the service is activated. The
+ * IME needs to be in the same package as the service and needs to be allowed by device
+ * policy, if there is one. The change will persist until the specified IME is next
+ * explicitly enabled or disabled by whatever means, such as user choice, and may persist
+ * beyond the life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to enable or disable. This IME must be installed.
+ * @param enabled {@code true} if the input method associated with {@code imeId} should be
+ * enabled.
+ * @return status code for the result of enabling/disabling the input method associated
+ * with {@code imeId}.
+ * @throws SecurityException if the input method is not in the same package as the service.
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ @CheckResult
+ @EnableImeResult
+ public int setInputMethodEnabled(@NonNull String imeId, boolean enabled)
+ throws SecurityException {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(mService).getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setInputMethodEnabled(imeId, enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return ENABLE_IME_FAIL_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the controller for the accessibility button within the system's navigation area.
+ * This instance may be used to query the accessibility button's state and register listeners
+ * for interactions with and state changes for the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * <p>
+ * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+ * within a navigation area, and as such, use of this class should be considered only as an
+ * optional feature or shortcut on supported device implementations.
+ * </p>
+ *
+ * @return the accessibility button controller for this {@link AccessibilityService}
+ */
+ @NonNull
+ public final AccessibilityButtonController getAccessibilityButtonController() {
+ return getAccessibilityButtonController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the controller of specified logical display for the accessibility button within the
+ * system's navigation area. This instance may be used to query the accessibility button's
+ * state and register listeners for interactions with and state changes for the accessibility
+ * button when {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * <p>
+ * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+ * within a navigation area, and as such, use of this class should be considered only as an
+ * optional feature or shortcut on supported device implementations.
+ * </p>
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for default
+ * display.
+ * @return the accessibility button controller for this {@link AccessibilityService}
+ */
+ @NonNull
+ public final AccessibilityButtonController getAccessibilityButtonController(int displayId) {
+ synchronized (mLock) {
+ AccessibilityButtonController controller = mAccessibilityButtonControllers.get(
+ displayId);
+ if (controller == null) {
+ controller = new AccessibilityButtonController(
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId));
+ mAccessibilityButtonControllers.put(displayId, controller);
+ }
+ return controller;
+ }
+ }
+
+ private void onAccessibilityButtonClicked(int displayId) {
+ getAccessibilityButtonController(displayId).dispatchAccessibilityButtonClicked();
+ }
+
+ private void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
+ available);
+ }
+
+ /** Sets the cache status.
+ *
+ * <p>If {@code enabled}, enable the cache and prefetching. Otherwise, disable the cache
+ * and prefetching.
+ * Note: By default the cache is enabled.
+ * @param enabled whether to enable or disable the cache.
+ * @return {@code true} if the cache and connection are not null, so the cache status is set.
+ */
+ public boolean setCacheEnabled(boolean enabled) {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ try {
+ connection.setCacheEnabled(enabled);
+ cache.setEnabled(enabled);
+ return true;
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting status of cache", re);
+ re.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /** Invalidates {@code node} and its subtree in the cache.
+ * @param node the node to invalidate.
+ * @return {@code true} if the subtree rooted at {@code node} was invalidated.
+ */
+ public boolean clearCachedSubtree(@NonNull AccessibilityNodeInfo node) {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ return cache.clearSubTree(node);
+ }
+
+ /** Clears the cache.
+ * @return {@code true} if the cache was cleared
+ */
+ public boolean clearCache() {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ cache.clear();
+ return true;
+ }
+
+ /** Checks if {@code node} is in the cache.
+ * @param node the node to check.
+ * @return {@code true} if {@code node} is in the cache.
+ */
+ public boolean isNodeInCache(@NonNull AccessibilityNodeInfo node) {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ return cache.isNodeInCache(node);
+ }
+
+ /** Returns {@code true} if the cache is enabled. */
+ public boolean isCacheEnabled() {
+ AccessibilityCache cache =
+ AccessibilityInteractionClient.getCache(mConnectionId);
+ if (cache == null) {
+ return false;
+ }
+ return cache.isEnabled();
+ }
+
+ /** This is called when the system action list is changed. */
+ public void onSystemActionsChanged() {
+ }
+
+ /**
+ * Returns a list of system actions available in the system right now.
+ * <p>
+ * System actions that correspond to the global action constants will have matching action IDs.
+ * For example, an with id {@link #GLOBAL_ACTION_BACK} will perform the back action.
+ * </p>
+ * <p>
+ * These actions should be called by {@link #performGlobalAction}.
+ * </p>
+ *
+ * @return A list of available system actions.
+ */
+ public final @NonNull List<AccessibilityAction> getSystemActions() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getSystemActions();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling getSystemActions", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Performs a global action. Such an action can be performed
+ * at any moment regardless of the current application or user
+ * location in that application. For example going back, going
+ * home, opening recents, etc.
+ *
+ * <p>
+ * Note: The global action ids themselves give no information about the current availability
+ * of their corresponding actions. To determine if a global action is available, use
+ * {@link #getSystemActions()}
+ *
+ * @param action The action to perform.
+ * @return Whether the action was successfully performed.
+ *
+ * Perform actions using ids like the id constants referenced below:
+ * @see #GLOBAL_ACTION_BACK
+ * @see #GLOBAL_ACTION_HOME
+ * @see #GLOBAL_ACTION_NOTIFICATIONS
+ * @see #GLOBAL_ACTION_RECENTS
+ * @see #GLOBAL_ACTION_DPAD_UP
+ * @see #GLOBAL_ACTION_DPAD_DOWN
+ * @see #GLOBAL_ACTION_DPAD_LEFT
+ * @see #GLOBAL_ACTION_DPAD_RIGHT
+ * @see #GLOBAL_ACTION_DPAD_CENTER
+ */
+ public final boolean performGlobalAction(int action) {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.performGlobalAction(action);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find the view that has the specified focus type. The search is performed
+ * across all windows.
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag. Otherwise, the search will be performed only in the active window.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> If the view with {@link AccessibilityNodeInfo#FOCUS_INPUT}
+ * is on an embedded view hierarchy which is embedded in a {@link android.view.SurfaceView} via
+ * {@link android.view.SurfaceView#setChildSurfacePackage}, there is a limitation that this API
+ * won't be able to find the node for the view. It's because views don't know about
+ * the embedded hierarchies. Instead, you could traverse all the nodes to find the
+ * focus.
+ * </p>
+ *
+ * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see AccessibilityNodeInfo#FOCUS_INPUT
+ * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ return AccessibilityInteractionClient.getInstance(this).findFocus(mConnectionId,
+ AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
+ }
+
+ /**
+ * Gets the an {@link AccessibilityServiceInfo} describing this
+ * {@link AccessibilityService}. This method is useful if one wants
+ * to change some of the dynamically configurable properties at
+ * runtime.
+ *
+ * @return The accessibility service info.
+ *
+ * @see AccessibilityServiceInfo
+ */
+ public final AccessibilityServiceInfo getServiceInfo() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getServiceInfo();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} that describes this service.
+ * <p>
+ * Note: You can call this method any time but the info will be picked up after
+ * the system has bound to this service and when this method is called thereafter.
+ *
+ * @param info The info.
+ */
+ public final void setServiceInfo(AccessibilityServiceInfo info) {
+ mInfo = info;
+ updateInputMethod(info);
+ mMotionEventSources = info.getMotionEventSources();
+ sendServiceInfo();
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+ * properly set and there is an {@link IAccessibilityServiceConnection} to the
+ * AccessibilityManagerService.
+ */
+ private void sendServiceInfo() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (mInfo != null && connection != null) {
+ if (!mInfo.isWithinParcelableSize()) {
+ throw new IllegalStateException(
+ "Cannot update service info: size is larger than safe parcelable limits.");
+ }
+ try {
+ connection.setServiceInfo(mInfo);
+ mInfo = null;
+ AccessibilityInteractionClient.getInstance(this).clearCache(mConnectionId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public Object getSystemService(@ServiceName @NonNull String name) {
+ if (getBaseContext() == null) {
+ throw new IllegalStateException(
+ "System services not available to Activities before onCreate()");
+ }
+
+ // Guarantee that we always return the same window manager instance.
+ if (WINDOW_SERVICE.equals(name)) {
+ if (mWindowManager == null) {
+ mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
+ final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+ // Set e default token obtained from the connection to ensure client could use
+ // accessibility overlay.
+ wm.setDefaultToken(mWindowToken);
+ }
+ return mWindowManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ /**
+ * Takes a screenshot of the specified display and returns it via an
+ * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+ * to construct the bitmap from the ScreenshotResult's payload.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshot your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ *
+ * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded or failed.
+ * See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshotOfWindow
+ */
+ public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+ @NonNull TakeScreenshotCallback callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection == null) {
+ sendScreenshotFailure(ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, executor, callback);
+ return;
+ }
+ try {
+ connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
+ final int status = result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS);
+ if (status != TAKE_SCREENSHOT_SUCCESS) {
+ sendScreenshotFailure(status, executor, callback);
+ return;
+ }
+ final HardwareBuffer hardwareBuffer =
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
+ final ParcelableColorSpace colorSpace =
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+ android.graphics.ParcelableColorSpace.class);
+ final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
+ colorSpace.getColorSpace(),
+ result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
+ sendScreenshotSuccess(screenshot, executor, callback);
+ }));
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * Takes a screenshot of the specified window and returns it via an
+ * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+ * to construct the bitmap from the ScreenshotResult's payload.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshots your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+ * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+ * visually underneath an accessibility overlay (from your or another accessibility service) in
+ * order to capture the window contents without the screenshot being covered by the overlay
+ * contents drawn on the screen.
+ * </p>
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded or failed.
+ * See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshot
+ */
+ public void takeScreenshotOfWindow(int accessibilityWindowId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TakeScreenshotCallback callback) {
+ AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+ mConnectionId, accessibilityWindowId, executor, callback);
+ }
+
+ /**
+ * Sets the strokeWidth and color of the accessibility focus rectangle.
+ * <p>
+ * <strong>Note:</strong> This setting persists until this or another active
+ * AccessibilityService changes it or the device reboots.
+ * </p>
+ *
+ * @param strokeWidth The stroke width of the rectangle in pixels.
+ * Setting this value to zero results in no focus rectangle being drawn.
+ * @param color The color of the rectangle.
+ */
+ public void setAccessibilityFocusAppearance(int strokeWidth, @ColorInt int color) {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setFocusAppearance(strokeWidth, color);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting the strokeWidth and color of the "
+ + "accessibility focus rectangle", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Implement to return the implementation of the internal accessibility
+ * service interface.
+ */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IAccessibilityServiceClientWrapper(this, getMainExecutor(), new Callbacks() {
+ @Override
+ public void onServiceConnected() {
+ AccessibilityService.this.dispatchServiceConnected();
+ }
+
+ @Override
+ public void onInterrupt() {
+ AccessibilityService.this.onInterrupt();
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ AccessibilityService.this.onAccessibilityEvent(event);
+ }
+
+ @Override
+ public void init(int connectionId, IBinder windowToken) {
+ mConnectionId = connectionId;
+ mWindowToken = windowToken;
+
+ // The client may have already obtained the window manager, so
+ // update the default token on whatever manager we gave them.
+ if (mWindowManager != null) {
+ final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+ wm.setDefaultToken(mWindowToken);
+ }
+ }
+
+ @Override
+ public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+ return AccessibilityService.this.onGesture(gestureEvent);
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ return AccessibilityService.this.onKeyEvent(event);
+ }
+
+ @Override
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ AccessibilityService.this.onMagnificationChanged(displayId, region, config);
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {
+ AccessibilityService.this.sendMotionEventToCallback(event);
+ }
+
+ @Override
+ public void onTouchStateChanged(int displayId, int state) {
+ AccessibilityService.this.onTouchStateChanged(displayId, state);
+ }
+
+ @Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
+ }
+
+ @Override
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
+ AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
+ }
+
+ @Override
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ AccessibilityService.this.onFingerprintCapturingGesturesChanged(active);
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) {
+ AccessibilityService.this.onFingerprintGesture(gesture);
+ }
+
+ @Override
+ public void onAccessibilityButtonClicked(int displayId) {
+ AccessibilityService.this.onAccessibilityButtonClicked(displayId);
+ }
+
+ @Override
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
+ }
+
+ @Override
+ public void onSystemActionsChanged() {
+ AccessibilityService.this.onSystemActionsChanged();
+ }
+
+ @Override
+ public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ if (mInputMethod != null) {
+ mInputMethod.createImeSession(callback);
+ }
+ }
+
+ @Override
+ public void startInput(@Nullable RemoteAccessibilityInputConnection connection,
+ @NonNull EditorInfo editorInfo, boolean restarting) {
+ if (mInputMethod != null) {
+ if (restarting) {
+ mInputMethod.restartInput(connection, editorInfo);
+ } else {
+ mInputMethod.startInput(connection, editorInfo);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Implements the internal {@link IAccessibilityServiceClient} interface to convert
+ * incoming calls to it back to calls on an {@link AccessibilityService}.
+ *
+ * @hide
+ */
+ public static class IAccessibilityServiceClientWrapper extends
+ IAccessibilityServiceClient.Stub {
+
+ private final Callbacks mCallback;
+ private final Context mContext;
+ private final Executor mExecutor;
+
+ private int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+ /**
+ * This is not {@code null} only between {@link #bindInput()} and {@link #unbindInput()} so
+ * that {@link RemoteAccessibilityInputConnection} can query if {@link #unbindInput()} has
+ * already been called or not, mainly to avoid unnecessary blocking operations.
+ *
+ * <p>This field must be set and cleared only from the binder thread(s), where the system
+ * guarantees that {@link #bindInput()},
+ * {@link #startInput(IRemoteAccessibilityInputConnection, EditorInfo, boolean)},
+ * and {@link #unbindInput()} are called with the same order as the original calls
+ * in {@link com.android.server.inputmethod.InputMethodManagerService}.
+ * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
+ */
+ @Nullable
+ CancellationGroup mCancellationGroup = null;
+
+ public IAccessibilityServiceClientWrapper(Context context, Executor executor,
+ Callbacks callback) {
+ mCallback = callback;
+ mContext = context;
+ mExecutor = executor;
+ }
+
+ public IAccessibilityServiceClientWrapper(Context context, Looper looper,
+ Callbacks callback) {
+ this(context, new HandlerExecutor(new Handler(looper)), callback);
+ }
+
+ public void init(IAccessibilityServiceConnection connection, int connectionId,
+ IBinder windowToken) {
+ mExecutor.execute(() -> {
+ mConnectionId = connectionId;
+ if (connection != null) {
+ AccessibilityInteractionClient.getInstance(mContext).addConnection(
+ mConnectionId, connection, /*initializeCache=*/true);
+ if (mContext != null) {
+ try {
+ connection.setAttributionTag(mContext.getAttributionTag());
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting attributionTag", re);
+ re.rethrowFromSystemServer();
+ }
+ }
+ mCallback.init(mConnectionId, windowToken);
+ mCallback.onServiceConnected();
+ } else {
+ AccessibilityInteractionClient.getInstance(mContext)
+ .clearCache(mConnectionId);
+ AccessibilityInteractionClient.getInstance(mContext).removeConnection(
+ mConnectionId);
+ mConnectionId = AccessibilityInteractionClient.NO_ID;
+ mCallback.init(AccessibilityInteractionClient.NO_ID, null);
+ }
+ return;
+ });
+ }
+
+ public void onInterrupt() {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onInterrupt();
+ }
+ });
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
+ mExecutor.execute(() -> {
+ if (event != null) {
+ // Send the event to AccessibilityCache via AccessibilityInteractionClient
+ AccessibilityInteractionClient.getInstance(mContext).onAccessibilityEvent(
+ event, mConnectionId);
+ if (serviceWantsEvent
+ && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
+ // Send the event to AccessibilityService
+ mCallback.onAccessibilityEvent(event);
+ }
+ }
+ return;
+ });
+ }
+
+ @Override
+ public void onGesture(AccessibilityGestureEvent gestureInfo) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onGesture(gestureInfo);
+ }
+ return;
+ });
+ }
+
+ public void clearAccessibilityCache() {
+ mExecutor.execute(() -> {
+ AccessibilityInteractionClient.getInstance(mContext).clearCache(mConnectionId);
+ return;
+ });
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int sequence) {
+ mExecutor.execute(() -> {
+ try {
+ IAccessibilityServiceConnection connection = AccessibilityInteractionClient
+ .getInstance(mContext).getConnection(mConnectionId);
+ if (connection != null) {
+ final boolean result = mCallback.onKeyEvent(event);
+ try {
+ connection.setOnKeyEventResult(result, sequence);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ } finally {
+ // Make sure the event is recycled.
+ try {
+ event.recycle();
+ } catch (IllegalStateException ise) {
+ /* ignore - best effort */
+ }
+ }
+ return;
+ });
+ }
+
+ /** Magnification changed callbacks for different displays */
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
+ MagnificationConfig config) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onMagnificationChanged(displayId, region, config);
+ }
+ return;
+ });
+ }
+
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onSoftKeyboardShowModeChanged(showMode);
+ }
+ return;
+ });
+ }
+
+ public void onPerformGestureResult(int sequence, boolean successfully) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onPerformGestureResult(sequence, successfully);
+ }
+ return;
+ });
+ }
+
+ public void onFingerprintCapturingGesturesChanged(boolean active) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onFingerprintCapturingGesturesChanged(active);
+ }
+ return;
+ });
+ }
+
+ public void onFingerprintGesture(int gesture) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onFingerprintGesture(gesture);
+ }
+ return;
+ });
+ }
+
+ /** Accessibility button clicked callbacks for different displays */
+ public void onAccessibilityButtonClicked(int displayId) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onAccessibilityButtonClicked(displayId);
+ }
+ return;
+ });
+ }
+
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onAccessibilityButtonAvailabilityChanged(available);
+ }
+ return;
+ });
+ }
+
+ /** This is called when the system action list is changed. */
+ public void onSystemActionsChanged() {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onSystemActionsChanged();
+ }
+ return;
+ });
+ }
+
+ /** This is called when an app requests ime sessions or when the service is enabled. */
+ public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.createImeSession(callback);
+ }
+ });
+ }
+
+ /**
+ * This is called when InputMethodManagerService requests to set the session enabled or
+ * disabled
+ */
+ public void setImeSessionEnabled(IAccessibilityInputMethodSession session,
+ boolean enabled) {
+ try {
+ AccessibilityInputMethodSession ls =
+ ((AccessibilityInputMethodSessionWrapper) session).getSession();
+ if (ls == null) {
+ Log.w(LOG_TAG, "Session is already finished: " + session);
+ return;
+ }
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ ls.setEnabled(enabled);
+ }
+ return;
+ });
+ } catch (ClassCastException e) {
+ Log.w(LOG_TAG, "Incoming session not of correct type: " + session, e);
+ }
+ }
+
+ /** This is called when an app binds input or when the service is enabled. */
+ public void bindInput() {
+ if (mCancellationGroup != null) {
+ Log.e(LOG_TAG, "bindInput must be paired with unbindInput.");
+ }
+ mCancellationGroup = new CancellationGroup();
+ }
+
+ /** This is called when an app unbinds input or when the service is disabled. */
+ public void unbindInput() {
+ if (mCancellationGroup != null) {
+ // Signal the flag then forget it.
+ mCancellationGroup.cancelAll();
+ mCancellationGroup = null;
+ } else {
+ Log.e(LOG_TAG, "unbindInput must be paired with bindInput.");
+ }
+ }
+
+ /** This is called when an app starts input or when the service is enabled. */
+ public void startInput(IRemoteAccessibilityInputConnection connection,
+ EditorInfo editorInfo, boolean restarting) {
+ if (mCancellationGroup == null) {
+ Log.e(LOG_TAG, "startInput must be called after bindInput.");
+ mCancellationGroup = new CancellationGroup();
+ }
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ final RemoteAccessibilityInputConnection ic = connection == null ? null
+ : new RemoteAccessibilityInputConnection(
+ connection, mCancellationGroup);
+ editorInfo.makeCompatible(mContext.getApplicationInfo().targetSdkVersion);
+ mCallback.startInput(ic, editorInfo, restarting);
+ }
+ });
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) {
+ mExecutor.execute(() -> {
+ mCallback.onMotionEvent(event);
+ });
+ }
+
+ @Override
+ public void onTouchStateChanged(int displayId, int state) {
+ mExecutor.execute(() -> {
+ mCallback.onTouchStateChanged(displayId, state);
+ });
+ }
+ }
+
+ /**
+ * Class used to report status of dispatched gestures
+ */
+ public static abstract class GestureResultCallback {
+ /** Called when the gesture has completed successfully
+ *
+ * @param gestureDescription The description of the gesture that completed.
+ */
+ public void onCompleted(GestureDescription gestureDescription) {
+ }
+
+ /** Called when the gesture was cancelled
+ *
+ * @param gestureDescription The description of the gesture that was cancelled.
+ */
+ public void onCancelled(GestureDescription gestureDescription) {
+ }
+ }
+
+ /* Object to keep track of gesture result callbacks */
+ private static class GestureResultCallbackInfo {
+ GestureDescription gestureDescription;
+ GestureResultCallback callback;
+ Handler handler;
+
+ GestureResultCallbackInfo(GestureDescription gestureDescription,
+ GestureResultCallback callback, Handler handler) {
+ this.gestureDescription = gestureDescription;
+ this.callback = callback;
+ this.handler = handler;
+ }
+ }
+
+ private void sendScreenshotSuccess(ScreenshotResult screenshot, Executor executor,
+ TakeScreenshotCallback callback) {
+ executor.execute(() -> callback.onSuccess(screenshot));
+ }
+
+ private void sendScreenshotFailure(@ScreenshotErrorCode int errorCode, Executor executor,
+ TakeScreenshotCallback callback) {
+ executor.execute(() -> callback.onFailure(errorCode));
+ }
+
+ /**
+ * Interface used to report status of taking screenshot.
+ */
+ public interface TakeScreenshotCallback {
+ /** Called when taking screenshot has completed successfully.
+ *
+ * @param screenshot The content of screenshot.
+ */
+ void onSuccess(@NonNull ScreenshotResult screenshot);
+
+ /** Called when taking screenshot has failed. {@code errorCode} will identify the
+ * reason of failure.
+ *
+ * @param errorCode The error code of this operation.
+ */
+ void onFailure(@ScreenshotErrorCode int errorCode);
+ }
+
+ /**
+ * Can be used to construct a bitmap of the screenshot or any other operations for
+ * {@link AccessibilityService#takeScreenshot} API.
+ */
+ public static final class ScreenshotResult {
+ private final @NonNull HardwareBuffer mHardwareBuffer;
+ private final @NonNull ColorSpace mColorSpace;
+ private final long mTimestamp;
+
+ /** @hide */
+ public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+ @NonNull ColorSpace colorSpace, long timestamp) {
+ Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
+ Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
+ mHardwareBuffer = hardwareBuffer;
+ mColorSpace = colorSpace;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * Gets the {@link ColorSpace} identifying a specific organization of colors of the
+ * screenshot.
+ *
+ * @return the color space
+ */
+ @NonNull
+ public ColorSpace getColorSpace() {
+ return mColorSpace;
+ }
+
+ /**
+ * Gets the {@link HardwareBuffer} representing a memory buffer of the screenshot.
+ * <p>
+ * <strong>Note:</strong> The application should call {@link HardwareBuffer#close()} when
+ * the buffer is no longer needed to free the underlying resources.
+ * </p>
+ *
+ * @return the hardware buffer
+ */
+ @NonNull
+ public HardwareBuffer getHardwareBuffer() {
+ return mHardwareBuffer;
+ }
+
+ /**
+ * Gets the timestamp of taking the screenshot.
+ *
+ * @return milliseconds of non-sleep uptime before screenshot since boot and it's from
+ * {@link SystemClock#uptimeMillis()}
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ };
+ }
+
+ /**
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+ * function requests that touch interactions starting in the specified region of the screen
+ * bypass the gesture detector. There can only be one gesture detection passthrough region per
+ * display. Requesting a new gesture detection passthrough region clears the existing one. To
+ * disable this passthrough and return to the original behavior, pass in an empty region. When
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this
+ * function has no effect.
+ *
+ * @param displayId The display on which to set this region.
+ * @param region the region of the screen.
+ */
+ public void setGestureDetectionPassthroughRegion(int displayId, @NonNull Region region) {
+ Preconditions.checkNotNull(region, "region cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setGestureDetectionPassthroughRegion(displayId, region);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+ * function requests that touch interactions starting in the specified region of the screen
+ * bypass the touch explorer and go straight to the view hierarchy. There can only be one touch
+ * exploration passthrough region per display. Requesting a new touch explorationpassthrough
+ * region clears the existing one. To disable this passthrough and return to the original
+ * behavior, pass in an empty region. When {@link
+ * AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this function has
+ * no effect.
+ *
+ * @param displayId The display on which to set this region.
+ * @param region the region of the screen .
+ */
+ public void setTouchExplorationPassthroughRegion(int displayId, @NonNull Region region) {
+ Preconditions.checkNotNull(region, "region cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setTouchExplorationPassthroughRegion(displayId, region);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Sets the system settings values that control the scaling factor for animations. The scale
+ * controls the animation playback speed for animations that respect these settings. Animations
+ * that do not respect the settings values will not be affected by this function. A lower scale
+ * value results in a faster speed. A value of <code>0</code> disables animations entirely. When
+ * animations are disabled services receive window change events more quickly which can reduce
+ * the potential by confusion by reducing the time during which windows are in transition.
+ *
+ * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED
+ * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE
+ * @param scale The scaling factor for all animations.
+ */
+ public void setAnimationScale(float scale) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setAnimationScale(scale);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ private static class AccessibilityContext extends ContextWrapper {
+ private final int mConnectionId;
+
+ private AccessibilityContext(Context base, int connectionId) {
+ super(base);
+ mConnectionId = connectionId;
+ setDefaultTokenInternal(this, getDisplayId());
+ }
+
+ @NonNull
+ @Override
+ public Context createDisplayContext(Display display) {
+ return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(int type, @Nullable Bundle options) {
+ final Context context = super.createWindowContext(type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(@NonNull Display display, int type,
+ @Nullable Bundle options) {
+ final Context context = super.createWindowContext(display, type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ private void setDefaultTokenInternal(Context context, int displayId) {
+ final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(
+ WINDOW_SERVICE);
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ IBinder token = null;
+ if (connection != null) {
+ try {
+ token = connection.getOverlayWindowToken(displayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to get window token", re);
+ re.rethrowFromSystemServer();
+ }
+ wm.setDefaultToken(token);
+ }
+ }
+ }
+
+ /**
+ * Returns the touch interaction controller for the specified logical display, which may be used
+ * to detect gestures and otherwise control touch interactions. If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled the
+ * controller's methods will have no effect.
+ *
+ * @param displayId The logical display id, use {@link Display#DEFAULT_DISPLAY} for default
+ * display.
+ * @return the TouchExploration controller
+ */
+ @NonNull
+ public final TouchInteractionController getTouchInteractionController(int displayId) {
+ synchronized (mLock) {
+ TouchInteractionController controller = mTouchInteractionControllers.get(displayId);
+ if (controller == null) {
+ controller = new TouchInteractionController(this, mLock, displayId);
+ mTouchInteractionControllers.put(displayId, controller);
+ }
+ return controller;
+ }
+ }
+
+ void sendMotionEventToCallback(MotionEvent event) {
+ boolean sendingTouchEventToTouchInteractionController = false;
+ if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+ TouchInteractionController controller;
+ synchronized (mLock) {
+ int displayId = event.getDisplayId();
+ controller = mTouchInteractionControllers.get(displayId);
+ }
+ if (controller != null) {
+ sendingTouchEventToTouchInteractionController = true;
+ controller.onMotionEvent(event);
+ }
+ }
+ final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+ if ((mMotionEventSources & eventSourceWithoutClass) != 0
+ && !sendingTouchEventToTouchInteractionController) {
+ onMotionEvent(event);
+ }
+ }
+
+ void onTouchStateChanged(int displayId, int state) {
+ TouchInteractionController controller;
+ synchronized (mLock) {
+ controller = mTouchInteractionControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.onStateChanged(state);
+ }
+ }
+
+ /**
+ * <p>Attaches a {@link android.view.SurfaceControl} containing an accessibility
+ * overlay to the
+ * specified display. This type of overlay should be used for content that does
+ * not need to
+ * track the location and size of Views in the currently active app e.g. service
+ * configuration
+ * or general service UI.</p>
+ * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}.
+ * To embed the View into a {@link android.view.SurfaceControl}, create a
+ * {@link android.view.SurfaceControlViewHost} and attach the View using
+ * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by
+ * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p>
+ * <p>To remove this overlay and free the associated
+ * resources, use
+ * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p>
+ * <p>If the specified overlay has already been attached to the specified display
+ * this method does nothing.
+ * If the specified overlay has already been attached to a previous display this
+ * function will transfer the overlay to the new display.
+ * Services can attach multiple overlays. Use
+ * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>.
+ * to coordinate the order of the overlays on screen.</p>
+ *
+ * @param displayId the display to which the SurfaceControl should be attached.
+ * @param sc the SurfaceControl containing the overlay content
+ */
+ public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ if (connection == null) {
+ return;
+ }
+ try {
+ connection.attachAccessibilityOverlayToDisplay(displayId, sc);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * <p>Attaches an accessibility overlay {@link android.view.SurfaceControl} to the
+ * specified
+ * window. This method should be used when you want the overlay to move and
+ * resize as the parent window moves and resizes.</p>
+ * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}.
+ * To embed the View into a {@link android.view.SurfaceControl}, create a
+ * {@link android.view.SurfaceControlViewHost} and attach the View using
+ * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by
+ * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p>
+ * <p>To remove this overlay and free the associated resources, use
+ * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p>
+ * <p>If the specified overlay has already been attached to the specified window
+ * this method does nothing.
+ * If the specified overlay has already been attached to a previous window this
+ * function will transfer the overlay to the new window.
+ * Services can attach multiple overlays. Use
+ * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>.
+ * to coordinate the order of the overlays on screen.</p>
+ *
+ * @param accessibilityWindowId The window id, from
+ * {@link AccessibilityWindowInfo#getId()}.
+ * @param sc the SurfaceControl containing the overlay
+ * content
+ */
+ public void attachAccessibilityOverlayToWindow(
+ int accessibilityWindowId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToWindow(mConnectionId, accessibilityWindowId, sc);
+ }
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityServiceInfo.java b/android-34/android/accessibilityservice/AccessibilityServiceInfo.java
new file mode 100644
index 0000000..d4a96b4
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,1655 @@
+/*
+ * 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.accessibilityservice;
+
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InputDevice;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+import com.android.internal.compat.IPlatformCompat;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class describes an {@link AccessibilityService}. The system notifies an
+ * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
+ * according to the information encapsulated in this class.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating AccessibilityServices, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityEventTypes
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFeedbackType
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFlags
+ * @attr ref android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ * @attr ref android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ * @attr ref android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ * @attr ref android.R.styleable#AccessibilityService_intro
+ * @attr ref android.R.styleable#AccessibilityService_description
+ * @attr ref android.R.styleable#AccessibilityService_summary
+ * @attr ref android.R.styleable#AccessibilityService_notificationTimeout
+ * @attr ref android.R.styleable#AccessibilityService_packageNames
+ * @attr ref android.R.styleable#AccessibilityService_settingsActivity
+ * @attr ref android.R.styleable#AccessibilityService_tileService
+ * @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
+ * @see AccessibilityService
+ * @see android.view.accessibility.AccessibilityEvent
+ * @see android.view.accessibility.AccessibilityManager
+ */
+public class AccessibilityServiceInfo implements Parcelable {
+
+ private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
+
+ /**
+ * Capability: This accessibility service can retrieve the active window content.
+ * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ */
+ public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001;
+
+ /**
+ * Capability: This accessibility service can request touch exploration mode in which
+ * touched items are spoken aloud and the UI can be explored via gestures.
+ * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ */
+ public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 0x00000002;
+
+ /**
+ * @deprecated No longer used
+ */
+ public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004;
+
+ /**
+ * Capability: This accessibility service can request to filter the key event stream.
+ * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ */
+ public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
+
+ /**
+ * Capability: This accessibility service can control display magnification.
+ * @see android.R.styleable#AccessibilityService_canControlMagnification
+ */
+ public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+
+ /**
+ * Capability: This accessibility service can perform gestures.
+ * @see android.R.styleable#AccessibilityService_canPerformGestures
+ */
+ public static final int CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020;
+
+ /**
+ * Capability: This accessibility service can capture gestures from the fingerprint sensor
+ * @see android.R.styleable#AccessibilityService_canRequestFingerprintGestures
+ */
+ public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
+
+ /**
+ * Capability: This accessibility service can take screenshot.
+ * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+ */
+ public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
+ private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
+
+ /**
+ * Denotes spoken feedback.
+ */
+ public static final int FEEDBACK_SPOKEN = 0x0000001;
+
+ /**
+ * Denotes haptic feedback.
+ */
+ public static final int FEEDBACK_HAPTIC = 0x0000002;
+
+ /**
+ * Denotes audible (not spoken) feedback.
+ */
+ public static final int FEEDBACK_AUDIBLE = 0x0000004;
+
+ /**
+ * Denotes visual feedback.
+ */
+ public static final int FEEDBACK_VISUAL = 0x0000008;
+
+ /**
+ * Denotes generic feedback.
+ */
+ public static final int FEEDBACK_GENERIC = 0x0000010;
+
+ /**
+ * Denotes braille feedback.
+ */
+ public static final int FEEDBACK_BRAILLE = 0x0000020;
+
+ /**
+ * Mask for all feedback types.
+ *
+ * @see #FEEDBACK_SPOKEN
+ * @see #FEEDBACK_HAPTIC
+ * @see #FEEDBACK_AUDIBLE
+ * @see #FEEDBACK_VISUAL
+ * @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_BRAILLE
+ */
+ public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
+
+ /**
+ * If an {@link AccessibilityService} is the default for a given type.
+ * Default service is invoked only if no package specific one exists. In case of
+ * more than one package specific service only the earlier registered is notified.
+ */
+ public static final int DEFAULT = 0x0000001;
+
+ /**
+ * If this flag is set the system will regard views that are not important
+ * for accessibility in addition to the ones that are important for accessibility.
+ * That is, views that are marked as not important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} or
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} and views that are
+ * marked as potentially important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
+ * that are not important for accessibility, are reported while querying the window
+ * content and also the accessibility service will receive accessibility events from
+ * them.
+ * <p>
+ * <strong>Note:</strong> For accessibility services targeting Android 4.1 (API level 16) or
+ * higher, this flag has to be explicitly set for the system to regard views that are not
+ * important for accessibility. For accessibility services targeting Android 4.0.4 (API level
+ * 15) or lower, this flag is ignored and all views are regarded for accessibility purposes.
+ * </p>
+ * <p>
+ * Usually views not important for accessibility are layout managers that do not
+ * react to user actions, do not draw any content, and do not have any special
+ * semantics in the context of the screen content. For example, a three by three
+ * grid can be implemented as three horizontal linear layouts and one vertical,
+ * or three vertical linear layouts and one horizontal, or one grid layout, etc.
+ * In this context, the actual layout managers used to achieve the grid configuration
+ * are not important; rather it is important that there are nine evenly distributed
+ * elements.
+ * </p>
+ */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
+
+ /**
+ * This flag requests that the system gets into touch exploration mode.
+ * In this mode a single finger moving on the screen behaves as a mouse
+ * pointer hovering over the user interface. The system will also detect
+ * certain gestures performed on the touch screen and notify this service.
+ * The system will enable touch exploration mode if there is at least one
+ * accessibility service that has this flag set. Hence, clearing this
+ * flag does not guarantee that the device will not be in touch exploration
+ * mode since there may be another enabled service that requested it.
+ * <p>
+ * For accessibility services targeting Android 4.3 (API level 18) or higher
+ * that want to set this flag have to declare this capability in their
+ * meta-data by setting the attribute
+ * {@link android.R.attr#canRequestTouchExplorationMode
+ * canRequestTouchExplorationMode} to true. Otherwise, this flag will
+ * be ignored. For how to declare the meta-data of a service refer to
+ * {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Services targeting Android 4.2.2 (API level 17) or lower will work
+ * normally. In other words, the first time they are run, if this flag is
+ * specified, a dialog is shown to the user to confirm enabling explore by
+ * touch.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ */
+ public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004;
+
+ /**
+ * @deprecated No longer used
+ */
+ public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
+
+ /**
+ * This flag requests that the {@link AccessibilityNodeInfo}s obtained
+ * by an {@link AccessibilityService} contain the id of the source view.
+ * The source view id will be a fully qualified resource name of the
+ * form "package:id/name", for example "foo.bar:id/my_list", and it is
+ * useful for UI test automation. This flag is not set by default.
+ */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+
+ /**
+ * This flag requests from the system to filter key events. If this flag
+ * is set the accessibility service will receive the key events before
+ * applications allowing it implement global shortcuts.
+ * <p>
+ * Services that want to set this flag have to declare this capability
+ * in their meta-data by setting the attribute {@link android.R.attr
+ * #canRequestFilterKeyEvents canRequestFilterKeyEvents} to true,
+ * otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ */
+ public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020;
+
+ /**
+ * This flag indicates to the system that the accessibility service wants
+ * to access content of all interactive windows. An interactive window is a
+ * window that has input focus or can be touched by a sighted user when explore
+ * by touch is not enabled. If this flag is not set your service will not receive
+ * {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED}
+ * events, calling AccessibilityService{@link AccessibilityService#getWindows()
+ * AccessibilityService.getWindows()} will return an empty list, and {@link
+ * AccessibilityNodeInfo#getWindow() AccessibilityNodeInfo.getWindow()} will
+ * return null.
+ * <p>
+ * Services that want to set this flag have to declare the capability
+ * to retrieve window content in their meta-data by setting the attribute
+ * {@link android.R.attr#canRetrieveWindowContent canRetrieveWindowContent} to
+ * true, otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ */
+ public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040;
+
+ /**
+ * This flag requests that all audio tracks system-wide with
+ * {@link android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY} be controlled by the
+ * {@link android.media.AudioManager#STREAM_ACCESSIBILITY} volume.
+ */
+ public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 0x00000080;
+
+ /**
+ * This flag indicates to the system that the accessibility service requests that an
+ * accessibility button be shown within the system's navigation area, if available.
+ * <p>
+ * <strong>Note:</strong> For accessibility services targeting APIs greater than
+ * {@link Build.VERSION_CODES#Q API 29}, this flag must be specified in the
+ * accessibility service metadata file. Otherwise, it will be ignored.
+ * </p>
+ */
+ public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x00000100;
+
+ /**
+ * This flag requests that all fingerprint gestures be sent to the accessibility service.
+ * <p>
+ * Services that want to set this flag have to declare the capability
+ * to retrieve window content in their meta-data by setting the attribute
+ * {@link android.R.attr#canRequestFingerprintGestures} to
+ * true, otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ *
+ * @see android.R.styleable#AccessibilityService_canRequestFingerprintGestures
+ * @see AccessibilityService#getFingerprintGestureController()
+ */
+ public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 0x00000200;
+
+ /**
+ * This flag requests that accessibility shortcut warning dialog has spoken feedback when
+ * dialog is shown.
+ */
+ public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 0x00000400;
+
+ /**
+ * This flag requests that when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+ * double tap and double tap and hold gestures are dispatched to the service rather than being
+ * handled by the framework. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this
+ * flag has no effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x0000800;
+
+ /**
+ * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+ * multi-finger gestures are also enabled. As a consequence, two-finger bypass gestures will be
+ * disabled. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no
+ * effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;
+
+ /**
+ * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled,
+ * two-finger passthrough gestures are re-enabled. Two-finger swipe gestures are not detected,
+ * but instead passed through as one-finger gestures. In addition, three-finger swipes from the
+ * bottom of the screen are not detected, and instead are passed through unchanged. If {@link
+ * #FLAG_REQUEST_MULTI_FINGER_GESTURES} is disabled this flag has no effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000;
+
+ /**
+ * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, a
+ * service will receive the motion events for each successfully-detected gesture. The service
+ * will also receive an AccessibilityGestureEvent of type GESTURE_INVALID for each cancelled
+ * gesture along with its motion events. A service will receive a gesture of type
+ * GESTURE_PASSTHROUGH and accompanying motion events for every passthrough gesture that does
+ * not start gesture detection. This information can be used to collect instances of improper
+ * gesture detection behavior and relay that information to framework developers. If {@link
+ * #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no effect.
+ *
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ public static final int FLAG_SEND_MOTION_EVENTS = 0x0004000;
+
+ /**
+ * This flag makes the AccessibilityService an input method editor with a subset of input
+ * method editor capabilities: get the {@link android.view.inputmethod.InputConnection} and get
+ * text selection change notifications.
+ *
+ * @see AccessibilityService#getInputMethod()
+ */
+ public static final int FLAG_INPUT_METHOD_EDITOR = 0x0008000;
+
+ /** {@hide} */
+ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
+
+ /**
+ * The event types an {@link AccessibilityService} is interested in.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_ANNOUNCEMENT
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED
+ */
+ public int eventTypes;
+
+ /**
+ * The package names an {@link AccessibilityService} is interested in. Setting
+ * to <code>null</code> is equivalent to all packages.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ */
+ public String[] packageNames;
+
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FEEDBACK_" }, value = {
+ FEEDBACK_AUDIBLE,
+ FEEDBACK_GENERIC,
+ FEEDBACK_HAPTIC,
+ FEEDBACK_SPOKEN,
+ FEEDBACK_VISUAL,
+ FEEDBACK_BRAILLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FeedbackType {}
+
+ /**
+ * The feedback type an {@link AccessibilityService} provides.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * @see #FEEDBACK_AUDIBLE
+ * @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_HAPTIC
+ * @see #FEEDBACK_SPOKEN
+ * @see #FEEDBACK_VISUAL
+ * @see #FEEDBACK_BRAILLE
+ */
+ @FeedbackType
+ public int feedbackType;
+
+ /**
+ * The timeout, in milliseconds, after the most recent event of a given type before an
+ * {@link AccessibilityService} is notified.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
+ * events to the client too frequently since this is accomplished via an expensive
+ * interprocess call. One can think of the timeout as a criteria to determine when
+ * event generation has settled down.
+ */
+ public long notificationTimeout;
+
+ /**
+ * This field represents a set of flags used for configuring an
+ * {@link AccessibilityService}.
+ * <p>
+ * <strong>Can be dynamically set at runtime.</strong>
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Accessibility services with targetSdkVersion greater than
+ * {@link Build.VERSION_CODES#Q API 29} cannot dynamically set the
+ * {@link #FLAG_REQUEST_ACCESSIBILITY_BUTTON} at runtime. It must be specified in the
+ * accessibility service metadata file.
+ * </p>
+ * @see #DEFAULT
+ * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ * @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+ * @see #FLAG_REQUEST_FILTER_KEY_EVENTS
+ * @see #FLAG_REPORT_VIEW_IDS
+ * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
+ * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
+ * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
+ * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
+ * @see #FLAG_INPUT_METHOD_EDITOR
+ */
+ public int flags;
+
+ /**
+ * Whether or not the service has crashed and is awaiting restart. Only valid from {@link
+ * android.view.accessibility.AccessibilityManager#getInstalledAccessibilityServiceList()},
+ * because that is populated from the internal list of running services.
+ *
+ * @hide
+ */
+ public boolean crashed;
+
+ /**
+ * A recommended timeout in milliseconds for non-interactive controls.
+ */
+ private int mNonInteractiveUiTimeout;
+
+ /**
+ * A recommended timeout in milliseconds for interactive controls.
+ */
+ private int mInteractiveUiTimeout;
+
+ /**
+ * The component name the accessibility service.
+ */
+ @NonNull
+ private ComponentName mComponentName;
+
+ /**
+ * The Service that implements this accessibility service component.
+ */
+ private ResolveInfo mResolveInfo;
+
+ /**
+ * The accessibility service setting activity's name, used by the system
+ * settings to launch the setting activity of this accessibility service.
+ */
+ private String mSettingsActivityName;
+
+ /**
+ * The name of {@link android.service.quicksettings.TileService} is associated with this
+ * accessibility service for one to one mapping. It is used by system settings to remind users
+ * this accessibility service has a {@link android.service.quicksettings.TileService}.
+ */
+ private String mTileServiceName;
+
+ /**
+ * Bit mask with capabilities of this service.
+ */
+ private int mCapabilities;
+
+ /**
+ * Resource id of the summary of the accessibility service.
+ */
+ private int mSummaryResId;
+
+ /**
+ * Non-localized summary of the accessibility service.
+ */
+ private String mNonLocalizedSummary;
+
+ /**
+ * Resource id of the intro of the accessibility service.
+ */
+ private int mIntroResId;
+
+ /**
+ * Resource id of the description of the accessibility service.
+ */
+ private int mDescriptionResId;
+
+ /**
+ * Non localized description of the accessibility service.
+ */
+ private String mNonLocalizedDescription;
+
+ /**
+ * For accessibility services targeting APIs greater than {@link Build.VERSION_CODES#Q API 29},
+ * {@link #FLAG_REQUEST_ACCESSIBILITY_BUTTON} must be specified in the accessibility service
+ * metadata file. Otherwise, it will be ignored.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q)
+ private static final long REQUEST_ACCESSIBILITY_BUTTON_CHANGE = 136293963L;
+
+ /**
+ * Resource id of the animated image of the accessibility service.
+ */
+ private int mAnimatedImageRes;
+
+ /**
+ * Resource id of the html description of the accessibility service.
+ */
+ private int mHtmlDescriptionRes;
+
+ /**
+ * Whether the service is for accessibility.
+ *
+ * @hide
+ */
+ private boolean mIsAccessibilityTool = false;
+
+ /**
+ * {@link InputDevice} sources which may send {@link android.view.MotionEvent}s.
+ * @see #setMotionEventSources(int)
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "SOURCE_" }, value = {
+ InputDevice.SOURCE_MOUSE,
+ InputDevice.SOURCE_STYLUS,
+ InputDevice.SOURCE_BLUETOOTH_STYLUS,
+ InputDevice.SOURCE_TRACKBALL,
+ InputDevice.SOURCE_MOUSE_RELATIVE,
+ InputDevice.SOURCE_TOUCHPAD,
+ InputDevice.SOURCE_TOUCH_NAVIGATION,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ InputDevice.SOURCE_JOYSTICK,
+ InputDevice.SOURCE_SENSOR
+ })
+ public @interface MotionEventSources {}
+
+ /**
+ * The bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ */
+ @MotionEventSources
+ private int mMotionEventSources = 0;
+
+ /**
+ * Creates a new instance.
+ */
+ public AccessibilityServiceInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param resolveInfo The service resolve info.
+ * @param context Context for accessing resources.
+ * @throws XmlPullParserException If a XML parsing error occurs.
+ * @throws IOException If a XML parsing error occurs.
+ *
+ * @hide
+ */
+ public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
+ throws XmlPullParserException, IOException {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ mComponentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ mResolveInfo = resolveInfo;
+
+ XmlResourceParser parser = null;
+
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ parser = serviceInfo.loadXmlMetaData(packageManager,
+ AccessibilityService.SERVICE_META_DATA);
+ if (parser == null) {
+ return;
+ }
+
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ String nodeName = parser.getName();
+ if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
+ throw new XmlPullParserException( "Meta-data does not start with"
+ + TAG_ACCESSIBILITY_SERVICE + " tag");
+ }
+
+ AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ Resources resources = packageManager.getResourcesForApplication(
+ serviceInfo.applicationInfo);
+ TypedArray asAttributes = resources.obtainAttributes(allAttributes,
+ com.android.internal.R.styleable.AccessibilityService);
+ eventTypes = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
+ 0);
+ String packageNamez = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_packageNames);
+ if (packageNamez != null) {
+ packageNames = packageNamez.split("(\\s)*,(\\s)*");
+ }
+ feedbackType = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
+ 0);
+ notificationTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
+ 0);
+ mNonInteractiveUiTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_nonInteractiveUiTimeout,
+ 0);
+ mInteractiveUiTimeout = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_interactiveUiTimeout,
+ 0);
+ flags = asAttributes.getInt(
+ com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
+ mSettingsActivityName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_settingsActivity);
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRetrieveWindowContent, false)) {
+ mCapabilities |= CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestTouchExplorationMode, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestFilterKeyEvents, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canControlMagnification, false)) {
+ mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canPerformGestures, false)) {
+ mCapabilities |= CAPABILITY_CAN_PERFORM_GESTURES;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestFingerprintGestures, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canTakeScreenshot, false)) {
+ mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+ }
+ TypedValue peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_description);
+ if (peekedValue != null) {
+ mDescriptionResId = peekedValue.resourceId;
+ CharSequence nonLocalizedDescription = peekedValue.coerceToString();
+ if (nonLocalizedDescription != null) {
+ mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
+ }
+ }
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_summary);
+ if (peekedValue != null) {
+ mSummaryResId = peekedValue.resourceId;
+ CharSequence nonLocalizedSummary = peekedValue.coerceToString();
+ if (nonLocalizedSummary != null) {
+ mNonLocalizedSummary = nonLocalizedSummary.toString().trim();
+ }
+ }
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_animatedImageDrawable);
+ if (peekedValue != null) {
+ mAnimatedImageRes = peekedValue.resourceId;
+ }
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_htmlDescription);
+ if (peekedValue != null) {
+ mHtmlDescriptionRes = peekedValue.resourceId;
+ }
+ mIsAccessibilityTool = asAttributes.getBoolean(
+ R.styleable.AccessibilityService_isAccessibilityTool, false);
+ mTileServiceName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityService_tileService);
+ peekedValue = asAttributes.peekValue(
+ com.android.internal.R.styleable.AccessibilityService_intro);
+ if (peekedValue != null) {
+ mIntroResId = peekedValue.resourceId;
+ }
+ asAttributes.recycle();
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException( "Unable to create context for: "
+ + serviceInfo.packageName);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ /**
+ * Updates the properties that an AccessibilityService can change dynamically.
+ * <p>
+ * Note: A11y services targeting APIs > Q, it cannot update flagRequestAccessibilityButton
+ * dynamically.
+ * </p>
+ *
+ * @param platformCompat The platform compat service to check the compatibility change.
+ * @param other The info from which to update the properties.
+ *
+ * @hide
+ */
+ public void updateDynamicallyConfigurableProperties(IPlatformCompat platformCompat,
+ AccessibilityServiceInfo other) {
+ if (isRequestAccessibilityButtonChangeEnabled(platformCompat)) {
+ other.flags &= ~FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ other.flags |= (flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ }
+ eventTypes = other.eventTypes;
+ packageNames = other.packageNames;
+ feedbackType = other.feedbackType;
+ notificationTimeout = other.notificationTimeout;
+ mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
+ mInteractiveUiTimeout = other.mInteractiveUiTimeout;
+ flags = other.flags;
+ mMotionEventSources = other.mMotionEventSources;
+ // NOTE: Ensure that only properties that are safe to be modified by the service itself
+ // are included here (regardless of hidden setters, etc.).
+ }
+
+ private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
+ if (mResolveInfo == null) {
+ return true;
+ }
+ try {
+ if (platformCompat != null) {
+ return platformCompat.isChangeEnabled(REQUEST_ACCESSIBILITY_BUTTON_CHANGE,
+ mResolveInfo.serviceInfo.applicationInfo);
+ }
+ } catch (RemoteException ignore) {
+ }
+ return mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.Q;
+ }
+
+ /**
+ * @hide
+ */
+ public void setComponentName(@NonNull ComponentName component) {
+ mComponentName = component;
+ }
+
+ /**
+ * @hide
+ */
+ public void setResolveInfo(@NonNull ResolveInfo resolveInfo) {
+ mResolveInfo = resolveInfo;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * The accessibility service id.
+ * <p>
+ * <strong>Generated by the system.</strong>
+ * </p>
+ * @return The id (or {@code null} if the component is not set yet).
+ */
+ public String getId() {
+ return mComponentName == null ? null : mComponentName.flattenToShortString();
+ }
+
+ /**
+ * The service {@link ResolveInfo}.
+ * <p>
+ * <strong>Generated by the system.</strong>
+ * </p>
+ * @return The info.
+ */
+ public ResolveInfo getResolveInfo() {
+ return mResolveInfo;
+ }
+
+ /**
+ * The settings activity name.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The settings activity name.
+ */
+ public String getSettingsActivityName() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Gets the name of {@link android.service.quicksettings.TileService} is associated with
+ * this accessibility service.
+ *
+ * @return The name of {@link android.service.quicksettings.TileService}.
+ */
+ @Nullable
+ public String getTileServiceName() {
+ return mTileServiceName;
+ }
+
+ /**
+ * Gets the animated image resource id.
+ *
+ * @return The animated image resource id.
+ *
+ * @hide
+ */
+ public int getAnimatedImageRes() {
+ return mAnimatedImageRes;
+ }
+
+ /**
+ * The animated image drawable.
+ * <p>
+ * Image can not exceed the screen size.
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The animated image drawable, or null if the resource is invalid or the image
+ * exceed the screen size.
+ *
+ * @hide
+ */
+ @Nullable
+ public Drawable loadAnimatedImage(@NonNull Context context) {
+ if (mAnimatedImageRes == /* invalid */ 0) {
+ return null;
+ }
+
+ return loadSafeAnimatedImage(context, mResolveInfo.serviceInfo.applicationInfo,
+ mAnimatedImageRes);
+ }
+
+ /**
+ * Whether this service can retrieve the current window's content.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return True if window content can be retrieved.
+ *
+ * @deprecated Use {@link #getCapabilities()}.
+ */
+ public boolean getCanRetrieveWindowContent() {
+ return (mCapabilities & CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
+ }
+
+ /**
+ * Returns the bit mask of capabilities this accessibility service has such as
+ * being able to retrieve the active window content, etc.
+ *
+ * @return The capability bit mask.
+ *
+ * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ * @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
+ */
+ public int getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Sets the bit mask of capabilities this accessibility service has such as
+ * being able to retrieve the active window content, etc.
+ *
+ * @param capabilities The capability bit mask.
+ *
+ * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ * @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ }
+
+ /**
+ * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ */
+ @MotionEventSources
+ public int getMotionEventSources() {
+ return mMotionEventSources;
+ }
+
+ /**
+ * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility
+ * service wants to listen to for generic {@link android.view.MotionEvent}s.
+ *
+ * <p>
+ * Including an {@link android.view.InputDevice} source that does not send
+ * {@link android.view.MotionEvent}s is effectively a no-op for that source, since you will
+ * not receive any events from that source.
+ * </p>
+ *
+ * <p>
+ * See {@link android.view.InputDevice} for complete source definitions.
+ * Many input devices send {@link android.view.InputEvent}s from more than one type of source so
+ * you may need to include multiple {@link android.view.MotionEvent} sources here, in addition
+ * to using {@link AccessibilityService#onKeyEvent} to listen to {@link android.view.KeyEvent}s.
+ * </p>
+ *
+ * <p>
+ * <strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits
+ * that complicate bitwise flag removal operations. To remove a specific source you should
+ * rebuild the entire value using bitwise OR operations on the individual source constants.
+ * </p>
+ *
+ * @param motionEventSources A bit mask of {@link android.view.InputDevice} sources.
+ * @see AccessibilityService#onMotionEvent
+ * @see #MotionEventSources
+ */
+ public void setMotionEventSources(@MotionEventSources int motionEventSources) {
+ mMotionEventSources = motionEventSources;
+ }
+
+ /**
+ * The localized summary of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized summary if available, and {@code null} if a summary
+ * has not been provided.
+ */
+ public CharSequence loadSummary(PackageManager packageManager) {
+ if (mSummaryResId == 0) {
+ return mNonLocalizedSummary;
+ }
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ CharSequence summary = packageManager.getText(serviceInfo.packageName,
+ mSummaryResId, serviceInfo.applicationInfo);
+ if (summary != null) {
+ return summary.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * The localized intro of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized intro if available, and {@code null} if a intro
+ * has not been provided.
+ */
+ @Nullable
+ public CharSequence loadIntro(@NonNull PackageManager packageManager) {
+ if (mIntroResId == /* invalid */ 0) {
+ return null;
+ }
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ CharSequence intro = packageManager.getText(serviceInfo.packageName,
+ mIntroResId, serviceInfo.applicationInfo);
+ if (intro != null) {
+ return intro.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the non-localized description of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The description.
+ *
+ * @deprecated Use {@link #loadDescription(PackageManager)}.
+ */
+ public String getDescription() {
+ return mNonLocalizedDescription;
+ }
+
+ /**
+ * The localized description of the accessibility service.
+ * <p>
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized description.
+ */
+ public String loadDescription(PackageManager packageManager) {
+ if (mDescriptionResId == 0) {
+ return mNonLocalizedDescription;
+ }
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ CharSequence description = packageManager.getText(serviceInfo.packageName,
+ mDescriptionResId, serviceInfo.applicationInfo);
+ if (description != null) {
+ return description.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * The localized and restricted html description of the accessibility service.
+ * <p>
+ * Filters the <img> tag which do not meet the custom specification and the <a> tag.
+ * <strong>Statically set from
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+ * </p>
+ * @return The localized and restricted html description.
+ *
+ * @hide
+ */
+ @Nullable
+ public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+ if (mHtmlDescriptionRes == /* invalid */ 0) {
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+ final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName,
+ mHtmlDescriptionRes, serviceInfo.applicationInfo);
+ if (htmlDescription != null) {
+ return getFilteredHtmlText(htmlDescription.toString().trim());
+ }
+ return null;
+ }
+
+ /**
+ * Set the recommended time that non-interactive controls need to remain on the screen to
+ * support the user.
+ * <p>
+ * <strong>This value can be dynamically set at runtime by
+ * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}.</strong>
+ * </p>
+ *
+ * @param timeout The timeout in milliseconds.
+ *
+ * @see android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
+ */
+ public void setNonInteractiveUiTimeoutMillis(@IntRange(from = 0) int timeout) {
+ mNonInteractiveUiTimeout = timeout;
+ }
+
+ /**
+ * Get the recommended timeout for non-interactive controls.
+ *
+ * @return The timeout in milliseconds.
+ *
+ * @see #setNonInteractiveUiTimeoutMillis(int)
+ */
+ public int getNonInteractiveUiTimeoutMillis() {
+ return mNonInteractiveUiTimeout;
+ }
+
+ /**
+ * Set the recommended time that interactive controls need to remain on the screen to
+ * support the user.
+ * <p>
+ * <strong>This value can be dynamically set at runtime by
+ * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}.</strong>
+ * </p>
+ *
+ * @param timeout The timeout in milliseconds.
+ *
+ * @see android.R.styleable#AccessibilityService_interactiveUiTimeout
+ */
+ public void setInteractiveUiTimeoutMillis(@IntRange(from = 0) int timeout) {
+ mInteractiveUiTimeout = timeout;
+ }
+
+ /**
+ * Get the recommended timeout for interactive controls.
+ *
+ * @return The timeout in milliseconds.
+ *
+ * @see #setInteractiveUiTimeoutMillis(int)
+ */
+ public int getInteractiveUiTimeoutMillis() {
+ return mInteractiveUiTimeout;
+ }
+
+ /** {@hide} */
+ public boolean isDirectBootAware() {
+ return ((flags & FLAG_FORCE_DIRECT_BOOT_AWARE) != 0)
+ || mResolveInfo.serviceInfo.directBootAware;
+ }
+
+ /**
+ * Sets whether the service is used to assist users with disabilities.
+ *
+ * <p>
+ * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}.
+ * </p>
+ *
+ * <p>
+ * This method is helpful for unit testing. However, this property is not dynamically
+ * configurable by a standard {@link AccessibilityService} so it's not possible to update the
+ * copy held by the system with this method.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setAccessibilityTool(boolean isAccessibilityTool) {
+ mIsAccessibilityTool = isAccessibilityTool;
+ }
+
+ /**
+ * Indicates if the service is used to assist users with disabilities.
+ *
+ * @return {@code true} if the property is set to true.
+ */
+ public boolean isAccessibilityTool() {
+ return mIsAccessibilityTool;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public final boolean isWithinParcelableSize() {
+ final Parcel parcel = Parcel.obtain();
+ writeToParcel(parcel, 0);
+ final boolean result = parcel.dataSize() <= IBinder.MAX_IPC_SIZE;
+ parcel.recycle();
+ return result;
+ }
+
+ public void writeToParcel(Parcel parcel, int flagz) {
+ parcel.writeInt(eventTypes);
+ parcel.writeStringArray(packageNames);
+ parcel.writeInt(feedbackType);
+ parcel.writeLong(notificationTimeout);
+ parcel.writeInt(mNonInteractiveUiTimeout);
+ parcel.writeInt(mInteractiveUiTimeout);
+ parcel.writeInt(flags);
+ parcel.writeInt(crashed ? 1 : 0);
+ parcel.writeParcelable(mComponentName, flagz);
+ parcel.writeParcelable(mResolveInfo, 0);
+ parcel.writeString(mSettingsActivityName);
+ parcel.writeInt(mCapabilities);
+ parcel.writeInt(mSummaryResId);
+ parcel.writeString(mNonLocalizedSummary);
+ parcel.writeInt(mDescriptionResId);
+ parcel.writeInt(mAnimatedImageRes);
+ parcel.writeInt(mHtmlDescriptionRes);
+ parcel.writeString(mNonLocalizedDescription);
+ parcel.writeBoolean(mIsAccessibilityTool);
+ parcel.writeString(mTileServiceName);
+ parcel.writeInt(mIntroResId);
+ parcel.writeInt(mMotionEventSources);
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ eventTypes = parcel.readInt();
+ packageNames = parcel.readStringArray();
+ feedbackType = parcel.readInt();
+ notificationTimeout = parcel.readLong();
+ mNonInteractiveUiTimeout = parcel.readInt();
+ mInteractiveUiTimeout = parcel.readInt();
+ flags = parcel.readInt();
+ crashed = parcel.readInt() != 0;
+ mComponentName = parcel.readParcelable(this.getClass().getClassLoader(), android.content.ComponentName.class);
+ mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
+ mSettingsActivityName = parcel.readString();
+ mCapabilities = parcel.readInt();
+ mSummaryResId = parcel.readInt();
+ mNonLocalizedSummary = parcel.readString();
+ mDescriptionResId = parcel.readInt();
+ mAnimatedImageRes = parcel.readInt();
+ mHtmlDescriptionRes = parcel.readInt();
+ mNonLocalizedDescription = parcel.readString();
+ mIsAccessibilityTool = parcel.readBoolean();
+ mTileServiceName = parcel.readString();
+ mIntroResId = parcel.readInt();
+ mMotionEventSources = parcel.readInt();
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AccessibilityServiceInfo other = (AccessibilityServiceInfo) obj;
+ if (mComponentName == null) {
+ if (other.mComponentName != null) {
+ return false;
+ }
+ } else if (!mComponentName.equals(other.mComponentName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ appendEventTypes(stringBuilder, eventTypes);
+ stringBuilder.append(", ");
+ appendPackageNames(stringBuilder, packageNames);
+ stringBuilder.append(", ");
+ appendFeedbackTypes(stringBuilder, feedbackType);
+ stringBuilder.append(", ");
+ stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
+ stringBuilder.append(", ");
+ stringBuilder.append("nonInteractiveUiTimeout: ").append(mNonInteractiveUiTimeout);
+ stringBuilder.append(", ");
+ stringBuilder.append("interactiveUiTimeout: ").append(mInteractiveUiTimeout);
+ stringBuilder.append(", ");
+ appendFlags(stringBuilder, flags);
+ stringBuilder.append(", ");
+ stringBuilder.append("id: ").append(getId());
+ stringBuilder.append(", ");
+ stringBuilder.append("resolveInfo: ").append(mResolveInfo);
+ stringBuilder.append(", ");
+ stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
+ stringBuilder.append(", ");
+ stringBuilder.append("tileServiceName: ").append(mTileServiceName);
+ stringBuilder.append(", ");
+ stringBuilder.append("summary: ").append(mNonLocalizedSummary);
+ stringBuilder.append(", ");
+ stringBuilder.append("isAccessibilityTool: ").append(mIsAccessibilityTool);
+ stringBuilder.append(", ");
+ appendCapabilities(stringBuilder, mCapabilities);
+ return stringBuilder.toString();
+ }
+
+ private static void appendFeedbackTypes(StringBuilder stringBuilder,
+ @FeedbackType int feedbackTypes) {
+ stringBuilder.append("feedbackTypes:");
+ stringBuilder.append("[");
+ while (feedbackTypes != 0) {
+ final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
+ stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
+ feedbackTypes &= ~feedbackTypeBit;
+ if (feedbackTypes != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
+ stringBuilder.append("packageNames:");
+ stringBuilder.append("[");
+ if (packageNames != null) {
+ final int packageNameCount = packageNames.length;
+ for (int i = 0; i < packageNameCount; i++) {
+ stringBuilder.append(packageNames[i]);
+ if (i < packageNameCount - 1) {
+ stringBuilder.append(", ");
+ }
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
+ stringBuilder.append("eventTypes:");
+ stringBuilder.append("[");
+ while (eventTypes != 0) {
+ final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
+ stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
+ eventTypes &= ~eventTypeBit;
+ if (eventTypes != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendFlags(StringBuilder stringBuilder, int flags) {
+ stringBuilder.append("flags:");
+ stringBuilder.append("[");
+ while (flags != 0) {
+ final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
+ stringBuilder.append(flagToString(flagBit));
+ flags &= ~flagBit;
+ if (flags != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ private static void appendCapabilities(StringBuilder stringBuilder, int capabilities) {
+ stringBuilder.append("capabilities:");
+ stringBuilder.append("[");
+ while (capabilities != 0) {
+ final int capabilityBit = (1 << Integer.numberOfTrailingZeros(capabilities));
+ stringBuilder.append(capabilityToString(capabilityBit));
+ capabilities &= ~capabilityBit;
+ if (capabilities != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
+ /**
+ * Returns the string representation of a feedback type. For example,
+ * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
+ *
+ * @param feedbackType The feedback type.
+ * @return The string representation.
+ */
+ public static String feedbackTypeToString(int feedbackType) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ while (feedbackType != 0) {
+ final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
+ feedbackType &= ~feedbackTypeFlag;
+ switch (feedbackTypeFlag) {
+ case FEEDBACK_AUDIBLE:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_AUDIBLE");
+ break;
+ case FEEDBACK_HAPTIC:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_HAPTIC");
+ break;
+ case FEEDBACK_GENERIC:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_GENERIC");
+ break;
+ case FEEDBACK_SPOKEN:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_SPOKEN");
+ break;
+ case FEEDBACK_VISUAL:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_VISUAL");
+ break;
+ case FEEDBACK_BRAILLE:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_BRAILLE");
+ break;
+ }
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /**
+ * Returns the string representation of a flag. For example,
+ * {@link #DEFAULT} is represented by the string DEFAULT.
+ *
+ * @param flag The flag.
+ * @return The string representation.
+ */
+ public static String flagToString(int flag) {
+ switch (flag) {
+ case DEFAULT:
+ return "DEFAULT";
+ case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS:
+ return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
+ case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
+ return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
+ case FLAG_SERVICE_HANDLES_DOUBLE_TAP:
+ return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
+ case FLAG_REQUEST_MULTI_FINGER_GESTURES:
+ return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
+ case FLAG_REQUEST_2_FINGER_PASSTHROUGH:
+ return "FLAG_REQUEST_2_FINGER_PASSTHROUGH";
+ case FLAG_SEND_MOTION_EVENTS:
+ return "FLAG_SEND_MOTION_EVENTS";
+ case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
+ return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+ case FLAG_REPORT_VIEW_IDS:
+ return "FLAG_REPORT_VIEW_IDS";
+ case FLAG_REQUEST_FILTER_KEY_EVENTS:
+ return "FLAG_REQUEST_FILTER_KEY_EVENTS";
+ case FLAG_RETRIEVE_INTERACTIVE_WINDOWS:
+ return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS";
+ case FLAG_ENABLE_ACCESSIBILITY_VOLUME:
+ return "FLAG_ENABLE_ACCESSIBILITY_VOLUME";
+ case FLAG_REQUEST_ACCESSIBILITY_BUTTON:
+ return "FLAG_REQUEST_ACCESSIBILITY_BUTTON";
+ case FLAG_REQUEST_FINGERPRINT_GESTURES:
+ return "FLAG_REQUEST_FINGERPRINT_GESTURES";
+ case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK:
+ return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK";
+ case FLAG_INPUT_METHOD_EDITOR:
+ return "FLAG_INPUT_METHOD_EDITOR";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the string representation of a capability. For example,
+ * {@link #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT} is represented
+ * by the string CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT.
+ *
+ * @param capability The capability.
+ * @return The string representation.
+ */
+ public static String capabilityToString(int capability) {
+ switch (capability) {
+ case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
+ return "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT";
+ case CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION:
+ return "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION";
+ case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
+ return "CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS";
+ case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
+ return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
+ case CAPABILITY_CAN_PERFORM_GESTURES:
+ return "CAPABILITY_CAN_PERFORM_GESTURES";
+ case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
+ return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+ case CAPABILITY_CAN_TAKE_SCREENSHOT:
+ return "CAPABILITY_CAN_TAKE_SCREENSHOT";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * @hide
+ * @return The list of {@link CapabilityInfo} objects.
+ * @deprecated The version that takes a context works better.
+ */
+ public List<CapabilityInfo> getCapabilityInfos() {
+ return getCapabilityInfos(null);
+ }
+
+ /**
+ * @hide
+ * @param context A valid context
+ * @return The list of {@link CapabilityInfo} objects.
+ */
+ public List<CapabilityInfo> getCapabilityInfos(Context context) {
+ if (mCapabilities == 0) {
+ return Collections.emptyList();
+ }
+ int capabilities = mCapabilities;
+ List<CapabilityInfo> capabilityInfos = new ArrayList<CapabilityInfo>();
+ SparseArray<CapabilityInfo> capabilityInfoSparseArray =
+ getCapabilityInfoSparseArray(context);
+ while (capabilities != 0) {
+ final int capabilityBit = 1 << Integer.numberOfTrailingZeros(capabilities);
+ capabilities &= ~capabilityBit;
+ CapabilityInfo capabilityInfo = capabilityInfoSparseArray.get(capabilityBit);
+ if (capabilityInfo != null) {
+ capabilityInfos.add(capabilityInfo);
+ }
+ }
+ return capabilityInfos;
+ }
+
+ private static SparseArray<CapabilityInfo> getCapabilityInfoSparseArray(Context context) {
+ if (sAvailableCapabilityInfos == null) {
+ sAvailableCapabilityInfos = new SparseArray<CapabilityInfo>();
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ R.string.capability_title_canRetrieveWindowContent,
+ R.string.capability_desc_canRetrieveWindowContent));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ R.string.capability_title_canRequestTouchExploration,
+ R.string.capability_desc_canRequestTouchExploration));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ R.string.capability_title_canRequestFilterKeyEvents,
+ R.string.capability_desc_canRequestFilterKeyEvents));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ R.string.capability_title_canControlMagnification,
+ R.string.capability_desc_canControlMagnification));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
+ R.string.capability_title_canPerformGestures,
+ R.string.capability_desc_canPerformGestures));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ R.string.capability_title_canTakeScreenshot,
+ R.string.capability_desc_canTakeScreenshot));
+ if ((context == null) || fingerprintAvailable(context)) {
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
+ R.string.capability_title_canCaptureFingerprintGestures,
+ R.string.capability_desc_canCaptureFingerprintGestures));
+ }
+ }
+ return sAvailableCapabilityInfos;
+ }
+
+ private static boolean fingerprintAvailable(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_FINGERPRINT)
+ && context.getSystemService(FingerprintManager.class).isHardwareDetected();
+ }
+ /**
+ * @hide
+ */
+ public static final class CapabilityInfo {
+ public final int capability;
+ public final int titleResId;
+ public final int descResId;
+
+ public CapabilityInfo(int capability, int titleResId, int descResId) {
+ this.capability = capability;
+ this.titleResId = titleResId;
+ this.descResId = descResId;
+ }
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityServiceInfo>() {
+ public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ public AccessibilityServiceInfo[] newArray(int size) {
+ return new AccessibilityServiceInfo[size];
+ }
+ };
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityShortcutInfo.java b/android-34/android/accessibilityservice/AccessibilityShortcutInfo.java
new file mode 100644
index 0000000..f53cfe4
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -0,0 +1,357 @@
+/*
+ * 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.accessibilityservice;
+
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Activities of interest to users with accessibility needs may request to be targets of the
+ * accessibility shortcut. These activities must handle the
+ * {@link Intent#ACTION_MAIN} intent with category
+ * {@link Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET}, which will be dispatched by the system
+ * when the user activates the shortcut when it is configured to point at this target.
+ *
+ * @see Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET
+ *
+ * @hide
+ */
+public final class AccessibilityShortcutInfo {
+ private static final String TAG_ACCESSIBILITY_SHORTCUT = "accessibility-shortcut-target";
+
+ /**
+ * Name under which an activity component of the accessibility shortcut publishes information
+ * about itself. This meta-data must reference an XML resource containing an
+ * <code><accessibility-shortcut-target></code> tag.
+ */
+ public static final String META_DATA = "android.accessibilityshortcut.target";
+
+ /**
+ * The component name of the accessibility shortcut target.
+ */
+ private final ComponentName mComponentName;
+
+ /**
+ * The activity info of the accessibility shortcut target.
+ */
+ private final ActivityInfo mActivityInfo;
+
+ /**
+ * Resource id of the intro of the accessibility shortcut target.
+ */
+ private final int mIntroResId;
+
+ /**
+ * Resource id of the summary of the accessibility shortcut target.
+ */
+ private final int mSummaryResId;
+
+ /**
+ * Resource id of the description of the accessibility shortcut target.
+ */
+ private final int mDescriptionResId;
+
+ /**
+ * Resource id of the animated image of the accessibility shortcut target.
+ */
+ private final int mAnimatedImageRes;
+
+ /**
+ * Resource id of the html description of the accessibility shortcut target.
+ */
+ private final int mHtmlDescriptionRes;
+
+ /**
+ * The accessibility shortcut target setting activity's name, used by the system
+ * settings to launch the setting activity of this accessibility shortcut target.
+ */
+ private String mSettingsActivityName;
+
+ /**
+ * The name of {@link android.service.quicksettings.TileService} is associated with this
+ * accessibility shortcut target for one to one mapping. It is used by system settings to remind
+ * users this accessibility service has a {@link android.service.quicksettings.TileService}.
+ */
+ private String mTileServiceName;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context Context for accessing resources.
+ * @param activityInfo The activity info.
+ * @throws XmlPullParserException If a XML parsing error occurs.
+ * @throws IOException If a XML parsing error occurs.
+ */
+ public AccessibilityShortcutInfo(@NonNull Context context, @NonNull ActivityInfo activityInfo)
+ throws XmlPullParserException, IOException {
+ final PackageManager packageManager = context.getPackageManager();
+ mComponentName = activityInfo.getComponentName();
+ mActivityInfo = activityInfo;
+
+ try (XmlResourceParser parser = mActivityInfo.loadXmlMetaData(
+ packageManager, META_DATA)) {
+ if (parser == null) {
+ throw new XmlPullParserException("Meta-data "
+ + TAG_ACCESSIBILITY_SHORTCUT + " does not exist");
+ }
+
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ final String nodeName = parser.getName();
+ if (!TAG_ACCESSIBILITY_SHORTCUT.equals(nodeName)) {
+ throw new XmlPullParserException("Meta-data does not start with"
+ + TAG_ACCESSIBILITY_SHORTCUT + " tag");
+ }
+
+ final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ final Resources resources = packageManager.getResourcesForApplication(
+ mActivityInfo.applicationInfo);
+ final TypedArray asAttributes = resources.obtainAttributes(allAttributes,
+ com.android.internal.R.styleable.AccessibilityShortcutTarget);
+
+ // Gets description
+ mDescriptionResId = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_description, 0);
+ // Gets summary
+ mSummaryResId = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0);
+ // Gets animated image
+ mAnimatedImageRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable
+ .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0);
+ // Gets html description
+ mHtmlDescriptionRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
+ 0);
+ // Get settings activity name
+ mSettingsActivityName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity);
+ // Get tile service class name
+ mTileServiceName = asAttributes.getString(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_tileService);
+ // Gets intro
+ mIntroResId = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_intro, 0);
+ asAttributes.recycle();
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new XmlPullParserException("Unable to create context for: "
+ + mActivityInfo.packageName);
+ }
+ }
+
+ /**
+ * The {@link ActivityInfo} of accessibility shortcut target.
+ *
+ * @return The activity info.
+ */
+ @NonNull
+ public ActivityInfo getActivityInfo() {
+ return mActivityInfo;
+ }
+
+ /**
+ * The {@link ComponentName} of the accessibility shortcut target.
+ *
+ * @return The component name
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * The localized summary of the accessibility shortcut target.
+ *
+ * @return The localized summary if available, and {@code null} if a summary
+ * has not been provided.
+ */
+ @Nullable
+ public String loadSummary(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mSummaryResId);
+ }
+
+ /**
+ * The localized intro of the accessibility shortcut target.
+ *
+ * @return The localized intro.
+ */
+ @Nullable
+ public String loadIntro(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mIntroResId);
+ }
+
+ /**
+ * The localized description of the accessibility shortcut target.
+ *
+ * @return The localized description.
+ */
+ @Nullable
+ public String loadDescription(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mDescriptionResId);
+ }
+
+ /**
+ * Gets the animated image resource id.
+ *
+ * @return The animated image resource id.
+ *
+ * @hide
+ */
+ public int getAnimatedImageRes() {
+ return mAnimatedImageRes;
+ }
+
+ /**
+ * The animated image drawable of the accessibility shortcut target.
+ *
+ * @return The animated image drawable, or null if the resource is invalid or the image
+ * exceed the screen size.
+ *
+ * @hide
+ */
+ @Nullable
+ public Drawable loadAnimatedImage(@NonNull Context context) {
+ if (mAnimatedImageRes == /* invalid */ 0) {
+ return null;
+ }
+
+ return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes);
+ }
+
+ /**
+ * The localized and restricted html description of the accessibility shortcut target.
+ * It filters the <img> tag which do not meet the custom specification and the <a> tag.
+ *
+ * @return The localized and restricted html description.
+ *
+ * @hide
+ */
+ @Nullable
+ public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+ final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
+ mHtmlDescriptionRes);
+ if (htmlDescription != null) {
+ return getFilteredHtmlText(htmlDescription);
+ }
+ return null;
+ }
+
+ /**
+ * The settings activity name.
+ *
+ * @return The settings activity name.
+ */
+ @Nullable
+ public String getSettingsActivityName() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Gets the name of {@link android.service.quicksettings.TileService} is associated with
+ * this accessibility shortcut target.
+ *
+ * @return The class name of {@link android.service.quicksettings.TileService}.
+ */
+ @Nullable
+ public String getTileServiceName() {
+ return mTileServiceName;
+ }
+
+ /**
+ * Gets string resource by the given activity and resource id.
+ */
+ @Nullable
+ private String loadResourceString(@NonNull PackageManager packageManager,
+ @NonNull ActivityInfo activityInfo, int resId) {
+ if (resId == 0) {
+ return null;
+ }
+ final CharSequence text = packageManager.getText(activityInfo.packageName,
+ resId, activityInfo.applicationInfo);
+ if (text != null) {
+ return text.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final AccessibilityShortcutInfo other = (AccessibilityShortcutInfo) obj;
+ if (mComponentName == null) {
+ if (other.mComponentName != null) {
+ return false;
+ }
+ } else if (!mComponentName.equals(other.mComponentName)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("AccessibilityShortcutInfo[");
+ stringBuilder.append("activityInfo: ").append(mActivityInfo);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+}
diff --git a/android-34/android/accessibilityservice/AccessibilityTrace.java b/android-34/android/accessibilityservice/AccessibilityTrace.java
new file mode 100644
index 0000000..f28015a
--- /dev/null
+++ b/android-34/android/accessibilityservice/AccessibilityTrace.java
@@ -0,0 +1,216 @@
+/**
+ * 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.accessibilityservice;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface to log accessibility trace.
+ *
+ * @hide
+ */
+public interface AccessibilityTrace {
+ String NAME_ACCESSIBILITY_SERVICE_CONNECTION = "IAccessibilityServiceConnection";
+ String NAME_ACCESSIBILITY_SERVICE_CLIENT = "IAccessibilityServiceClient";
+ String NAME_ACCESSIBILITY_MANAGER = "IAccessibilityManager";
+ String NAME_ACCESSIBILITY_MANAGER_CLIENT = "IAccessibilityManagerClient";
+ String NAME_ACCESSIBILITY_INTERACTION_CONNECTION = "IAccessibilityInteractionConnection";
+ String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK =
+ "IAccessibilityInteractionConnectionCallback";
+ String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback";
+ String NAME_WINDOW_MAGNIFICATION_CONNECTION = "IWindowMagnificationConnection";
+ String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback";
+ String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal";
+ String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback";
+ String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks";
+ String NAME_INPUT_FILTER = "InputFilter";
+ String NAME_GESTURE = "Gesture";
+ String NAME_ACCESSIBILITY_SERVICE = "AccessibilityService";
+ String NAME_PACKAGE_BROADCAST_RECEIVER = "PMBroadcastReceiver";
+ String NAME_USER_BROADCAST_RECEIVER = "UserBroadcastReceiver";
+ String NAME_FINGERPRINT = "FingerprintGesture";
+ String NAME_ACCESSIBILITY_INTERACTION_CLIENT = "AccessibilityInteractionClient";
+
+ String NAME_ALL_LOGGINGS = "AllLoggings";
+ String NAME_NONE = "None";
+
+ long FLAGS_ACCESSIBILITY_SERVICE_CONNECTION = 0x0000000000000001L;
+ long FLAGS_ACCESSIBILITY_SERVICE_CLIENT = 0x0000000000000002L;
+ long FLAGS_ACCESSIBILITY_MANAGER = 0x0000000000000004L;
+ long FLAGS_ACCESSIBILITY_MANAGER_CLIENT = 0x0000000000000008L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L;
+ long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L;
+ long FLAGS_WINDOW_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
+ long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
+ long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L;
+ long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L;
+ long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L;
+ long FLAGS_INPUT_FILTER = 0x0000000000001000L;
+ long FLAGS_GESTURE = 0x0000000000002000L;
+ long FLAGS_ACCESSIBILITY_SERVICE = 0x0000000000004000L;
+ long FLAGS_PACKAGE_BROADCAST_RECEIVER = 0x0000000000008000L;
+ long FLAGS_USER_BROADCAST_RECEIVER = 0x0000000000010000L;
+ long FLAGS_FINGERPRINT = 0x0000000000020000L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CLIENT = 0x0000000000040000L;
+
+ long FLAGS_LOGGING_NONE = 0x0000000000000000L;
+ long FLAGS_LOGGING_ALL = 0xFFFFFFFFFFFFFFFFL;
+
+ long FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES = FLAGS_ACCESSIBILITY_INTERACTION_CLIENT
+ | FLAGS_ACCESSIBILITY_SERVICE
+ | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION
+ | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+
+ Map<String, Long> sNamesToFlags = Map.ofEntries(
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE_CONNECTION, FLAGS_ACCESSIBILITY_SERVICE_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE_CLIENT, FLAGS_ACCESSIBILITY_SERVICE_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_MANAGER, FLAGS_ACCESSIBILITY_MANAGER),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_MANAGER_CLIENT, FLAGS_ACCESSIBILITY_MANAGER_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CONNECTION,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK,
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MAGNIFICATION_CONNECTION, FLAGS_WINDOW_MAGNIFICATION_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_MAGNIFICATION_CALLBACK, FLAGS_MAGNIFICATION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_INPUT_FILTER, FLAGS_INPUT_FILTER),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_GESTURE, FLAGS_GESTURE),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE, FLAGS_ACCESSIBILITY_SERVICE),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_USER_BROADCAST_RECEIVER, FLAGS_USER_BROADCAST_RECEIVER),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_FINGERPRINT, FLAGS_FINGERPRINT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CLIENT, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_NONE, FLAGS_LOGGING_NONE),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_ALL_LOGGINGS, FLAGS_LOGGING_ALL));
+
+ /**
+ * Get the flags of the logging types by the given names.
+ * The names list contains logging type names in lower case.
+ */
+ static long getLoggingFlagsFromNames(List<String> names) {
+ long types = FLAGS_LOGGING_NONE;
+ for (String name : names) {
+ long flag = sNamesToFlags.get(name);
+ types |= flag;
+ }
+ return types;
+ }
+
+ /**
+ * Get the list of the names of logging types by the given flags.
+ */
+ static List<String> getNamesOfLoggingTypes(long flags) {
+ List<String> list = new ArrayList<String>();
+
+ for (Map.Entry<String, Long> entry : sNamesToFlags.entrySet()) {
+ if ((entry.getValue() & flags) != FLAGS_LOGGING_NONE) {
+ list.add(entry.getKey());
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Whether the trace is enabled for any logging type.
+ */
+ boolean isA11yTracingEnabled();
+
+ /**
+ * Whether the trace is enabled for any of the given logging type.
+ */
+ boolean isA11yTracingEnabledForTypes(long typeIdFlags);
+
+ /**
+ * Get trace state to be sent to AccessibilityManager.
+ */
+ int getTraceStateForAccessibilityManagerClientState();
+
+ /**
+ * Start tracing for the given logging types.
+ */
+ void startTrace(long flagss);
+
+ /**
+ * Stop tracing.
+ */
+ void stopTrace();
+
+ /**
+ * Log one trace entry.
+ * @param where A string to identify this log entry, which can be used to search through the
+ * tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ */
+ void logTrace(String where, long loggingFlags);
+
+ /**
+ * Log one trace entry.
+ * @param where A string to identify this log entry, which can be used to filter/search
+ * through the tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ * @param callingParams The parameters for the method to be logged.
+ */
+ void logTrace(String where, long loggingFlags, String callingParams);
+
+ /**
+ * Log one trace entry. Accessibility services using AccessibilityInteractionClient to
+ * make screen content related requests use this API to log entry when receive callback.
+ * @param timestamp The timestamp when a callback is received.
+ * @param where A string to identify this log entry, which can be used to filter/search
+ * through the tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ * @param callingParams The parameters for the callback.
+ * @param processId The process id of the calling component.
+ * @param threadId The threadId of the calling component.
+ * @param callingUid The calling uid of the callback.
+ * @param callStack The call stack of the callback.
+ * @param ignoreStackElements ignore these call stack element
+ */
+ void logTrace(long timestamp, String where, long loggingFlags, String callingParams,
+ int processId, long threadId, int callingUid, StackTraceElement[] callStack,
+ Set<String> ignoreStackElements);
+}
diff --git a/android-34/android/accessibilityservice/FingerprintGestureController.java b/android-34/android/accessibilityservice/FingerprintGestureController.java
new file mode 100644
index 0000000..c30030d
--- /dev/null
+++ b/android-34/android/accessibilityservice/FingerprintGestureController.java
@@ -0,0 +1,185 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An {@link AccessibilityService} can capture gestures performed on a device's fingerprint
+ * sensor, as long as the device has a sensor capable of detecting gestures.
+ * <p>
+ * This capability must be declared by the service as
+ * {@link AccessibilityServiceInfo#CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES}. It also requires
+ * the permission {@link android.Manifest.permission#USE_FINGERPRINT}.
+ * <p>
+ * Because capturing fingerprint gestures may have side effects, services with the capability only
+ * capture gestures when {@link AccessibilityServiceInfo#FLAG_REQUEST_FINGERPRINT_GESTURES} is set.
+ * <p>
+ * <strong>Note: </strong>The fingerprint sensor is used for authentication in critical use cases,
+ * so services must carefully design their user's experience when performing gestures on the sensor.
+ * When the sensor is in use by an app, for example, when authenticating or enrolling a user,
+ * the sensor will not detect gestures. Services need to ensure that users understand when the
+ * sensor is in-use for authentication to prevent users from authenticating unintentionally when
+ * trying to interact with the service. They can use
+ * {@link FingerprintGestureCallback#onGestureDetectionAvailabilityChanged(boolean)} to learn when
+ * gesture detection becomes unavailable.
+ * <p>
+ * Multiple accessibility services may listen for fingerprint gestures simultaneously, so services
+ * should provide a way for the user to disable the use of this feature so multiple services don't
+ * conflict with each other.
+ * <p>
+ * {@see android.hardware.fingerprint.FingerprintManager#isHardwareDetected}
+ */
+public final class FingerprintGestureController {
+ /** Identifier for a swipe right on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_RIGHT = 0x00000001;
+
+ /** Identifier for a swipe left on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_LEFT = 0x00000002;
+
+ /** Identifier for a swipe up on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_UP = 0x00000004;
+
+ /** Identifier for a swipe down on the fingerprint sensor */
+ public static final int FINGERPRINT_GESTURE_SWIPE_DOWN = 0x00000008;
+
+ private static final String LOG_TAG = "FingerprintGestureController";
+ private final Object mLock = new Object();
+ private final IAccessibilityServiceConnection mAccessibilityServiceConnection;
+
+ private final ArrayMap<FingerprintGestureCallback, Handler> mCallbackHandlerMap =
+ new ArrayMap<>(1);
+
+ /**
+ * @param connection The connection to use for system interactions
+ * @hide
+ */
+ @VisibleForTesting
+ public FingerprintGestureController(IAccessibilityServiceConnection connection) {
+ mAccessibilityServiceConnection = connection;
+ }
+
+ /**
+ * Gets if the fingerprint sensor's gesture detection is available.
+ *
+ * @return {@code true} if the sensor's gesture detection is available. {@code false} if it is
+ * not currently detecting gestures (for example, if it is enrolling a finger).
+ */
+ public boolean isGestureDetectionAvailable() {
+ try {
+ return mAccessibilityServiceConnection.isFingerprintGestureDetectionAvailable();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to check if fingerprint gestures are active", re);
+ re.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Register a callback to be informed of fingerprint sensor gesture events.
+ *
+ * @param callback The listener to be added.
+ * @param handler The handler to use for the callback. If {@code null}, callbacks will happen
+ * on the service's main thread.
+ */
+ public void registerFingerprintGestureCallback(
+ @NonNull FingerprintGestureCallback callback, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mCallbackHandlerMap.put(callback, handler);
+ }
+ }
+
+ /**
+ * Unregister a listener added with {@link #registerFingerprintGestureCallback}.
+ *
+ * @param callback The callback to remove. Removing a callback that was never added has no
+ * effect.
+ */
+ public void unregisterFingerprintGestureCallback(FingerprintGestureCallback callback) {
+ synchronized (mLock) {
+ mCallbackHandlerMap.remove(callback);
+ }
+ }
+
+ /**
+ * Called when gesture detection becomes active or inactive
+ * @hide
+ */
+ public void onGestureDetectionActiveChanged(boolean active) {
+ final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
+ synchronized (mLock) {
+ handlerMap = new ArrayMap<>(mCallbackHandlerMap);
+ }
+ int numListeners = handlerMap.size();
+ for (int i = 0; i < numListeners; i++) {
+ FingerprintGestureCallback callback = handlerMap.keyAt(i);
+ Handler handler = handlerMap.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onGestureDetectionAvailabilityChanged(active));
+ } else {
+ callback.onGestureDetectionAvailabilityChanged(active);
+ }
+ }
+ }
+
+ /**
+ * Called when gesture is detected.
+ * @hide
+ */
+ public void onGesture(int gesture) {
+ final ArrayMap<FingerprintGestureCallback, Handler> handlerMap;
+ synchronized (mLock) {
+ handlerMap = new ArrayMap<>(mCallbackHandlerMap);
+ }
+ int numListeners = handlerMap.size();
+ for (int i = 0; i < numListeners; i++) {
+ FingerprintGestureCallback callback = handlerMap.keyAt(i);
+ Handler handler = handlerMap.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onGestureDetected(gesture));
+ } else {
+ callback.onGestureDetected(gesture);
+ }
+ }
+ }
+
+ /**
+ * Class that is called back when fingerprint gestures are being used for accessibility.
+ */
+ public abstract static class FingerprintGestureCallback {
+ /**
+ * Called when the fingerprint sensor's gesture detection becomes available or unavailable.
+ *
+ * @param available Whether or not the sensor's gesture detection is now available.
+ */
+ public void onGestureDetectionAvailabilityChanged(boolean available) {}
+
+ /**
+ * Called when the fingerprint sensor detects gestures.
+ *
+ * @param gesture The id of the gesture that was detected. For example,
+ * {@link #FINGERPRINT_GESTURE_SWIPE_RIGHT}.
+ */
+ public void onGestureDetected(int gesture) {}
+ }
+}
diff --git a/android-34/android/accessibilityservice/GestureDescription.java b/android-34/android/accessibilityservice/GestureDescription.java
new file mode 100644
index 0000000..857c541
--- /dev/null
+++ b/android-34/android/accessibilityservice/GestureDescription.java
@@ -0,0 +1,603 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Accessibility services with the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
+ * gestures. This class describes those gestures. Gestures are made up of one or more strokes.
+ * Gestures are immutable once built and will be dispatched to the specified display.
+ * <p>
+ * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
+ */
+public final class GestureDescription {
+ /** Gestures may contain no more than this many strokes */
+ private static final int MAX_STROKE_COUNT = 20;
+
+ /**
+ * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
+ */
+ private static final long MAX_GESTURE_DURATION_MS = 60 * 1000;
+
+ private final List<StrokeDescription> mStrokes = new ArrayList<>();
+ private final float[] mTempPos = new float[2];
+ private final int mDisplayId;
+
+ /**
+ * Get the upper limit for the number of strokes a gesture may contain.
+ *
+ * @return The maximum number of strokes.
+ */
+ public static int getMaxStrokeCount() {
+ return MAX_STROKE_COUNT;
+ }
+
+ /**
+ * Get the upper limit on a gesture's duration.
+ *
+ * @return The maximum duration in milliseconds.
+ */
+ public static long getMaxGestureDuration() {
+ return MAX_GESTURE_DURATION_MS;
+ }
+
+ private GestureDescription() {
+ this(new ArrayList<>());
+ }
+
+ private GestureDescription(List<StrokeDescription> strokes) {
+ this(strokes, Display.DEFAULT_DISPLAY);
+ }
+
+ private GestureDescription(List<StrokeDescription> strokes, int displayId) {
+ mStrokes.addAll(strokes);
+ mDisplayId = displayId;
+ }
+
+ /**
+ * Get the number of stroke in the gesture.
+ *
+ * @return the number of strokes in this gesture
+ */
+ public int getStrokeCount() {
+ return mStrokes.size();
+ }
+
+ /**
+ * Read a stroke from the gesture
+ *
+ * @param index the index of the stroke
+ *
+ * @return A description of the stroke.
+ */
+ public StrokeDescription getStroke(@IntRange(from = 0) int index) {
+ return mStrokes.get(index);
+ }
+
+ /**
+ * Returns the ID of the display this gesture is sent on, for use with
+ * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+ *
+ * @return The logical display id.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Return the smallest key point (where a path starts or ends) that is at least a specified
+ * offset
+ * @param offset the minimum start time
+ * @return The next key time that is at least the offset or -1 if one can't be found
+ */
+ private long getNextKeyPointAtLeast(long offset) {
+ long nextKeyPoint = Long.MAX_VALUE;
+ for (int i = 0; i < mStrokes.size(); i++) {
+ long thisStartTime = mStrokes.get(i).mStartTime;
+ if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) {
+ nextKeyPoint = thisStartTime;
+ }
+ long thisEndTime = mStrokes.get(i).mEndTime;
+ if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) {
+ nextKeyPoint = thisEndTime;
+ }
+ }
+ return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint;
+ }
+
+ /**
+ * Get the points that correspond to a particular moment in time.
+ * @param time The time of interest
+ * @param touchPoints An array to hold the current touch points. Must be preallocated to at
+ * least the number of paths in the gesture to prevent going out of bounds
+ * @return The number of points found, and thus the number of elements set in each array
+ */
+ private int getPointsForTime(long time, TouchPoint[] touchPoints) {
+ int numPointsFound = 0;
+ for (int i = 0; i < mStrokes.size(); i++) {
+ StrokeDescription strokeDescription = mStrokes.get(i);
+ if (strokeDescription.hasPointForTime(time)) {
+ touchPoints[numPointsFound].mStrokeId = strokeDescription.getId();
+ touchPoints[numPointsFound].mContinuedStrokeId =
+ strokeDescription.getContinuedStrokeId();
+ touchPoints[numPointsFound].mIsStartOfPath =
+ (strokeDescription.getContinuedStrokeId() < 0)
+ && (time == strokeDescription.mStartTime);
+ touchPoints[numPointsFound].mIsEndOfPath = !strokeDescription.willContinue()
+ && (time == strokeDescription.mEndTime);
+ strokeDescription.getPosForTime(time, mTempPos);
+ touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
+ touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
+ numPointsFound++;
+ }
+ }
+ return numPointsFound;
+ }
+
+ // Total duration assumes that the gesture starts at 0; waiting around to start a gesture
+ // counts against total duration
+ private static long getTotalDuration(List<StrokeDescription> paths) {
+ long latestEnd = Long.MIN_VALUE;
+ for (int i = 0; i < paths.size(); i++) {
+ StrokeDescription path = paths.get(i);
+ latestEnd = Math.max(latestEnd, path.mEndTime);
+ }
+ return Math.max(latestEnd, 0);
+ }
+
+ /**
+ * Builder for a {@code GestureDescription}
+ */
+ public static class Builder {
+
+ private final List<StrokeDescription> mStrokes = new ArrayList<>();
+ private int mDisplayId = Display.DEFAULT_DISPLAY;
+
+ /**
+ * Adds a stroke to the gesture description. Up to
+ * {@link GestureDescription#getMaxStrokeCount()} paths may be
+ * added to a gesture, and the total gesture duration (earliest path start time to latest
+ * path end time) may not exceed {@link GestureDescription#getMaxGestureDuration()}.
+ *
+ * @param strokeDescription the stroke to add.
+ *
+ * @return this
+ */
+ public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
+ if (mStrokes.size() >= MAX_STROKE_COUNT) {
+ throw new IllegalStateException(
+ "Attempting to add too many strokes to a gesture. Maximum is "
+ + MAX_STROKE_COUNT
+ + ", got "
+ + mStrokes.size());
+ }
+
+ mStrokes.add(strokeDescription);
+
+ if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) {
+ mStrokes.remove(strokeDescription);
+ throw new IllegalStateException(
+ "Gesture would exceed maximum duration with new stroke");
+ }
+ return this;
+ }
+
+ /**
+ * Sets the id of the display to dispatch gestures.
+ *
+ * @param displayId The logical display id
+ *
+ * @return this
+ */
+ public @NonNull Builder setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ return this;
+ }
+
+ public GestureDescription build() {
+ if (mStrokes.size() == 0) {
+ throw new IllegalStateException("Gestures must have at least one stroke");
+ }
+ return new GestureDescription(mStrokes, mDisplayId);
+ }
+ }
+
+ /**
+ * Immutable description of stroke that can be part of a gesture.
+ */
+ public static class StrokeDescription {
+ private static final int INVALID_STROKE_ID = -1;
+
+ static int sIdCounter;
+
+ Path mPath;
+ long mStartTime;
+ long mEndTime;
+ private float mTimeToLengthConversion;
+ private PathMeasure mPathMeasure;
+ // The tap location is only set for zero-length paths
+ float[] mTapLocation;
+ int mId;
+ boolean mContinued;
+ int mContinuedStrokeId = INVALID_STROKE_ID;
+
+ /**
+ * @param path The path to follow. Must have exactly one contour. The bounds of the path
+ * must not be negative. The path must not be empty. If the path has zero length
+ * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time the stroke should start. Must not be negative.
+ * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+ * Must be positive.
+ */
+ public StrokeDescription(@NonNull Path path,
+ @IntRange(from = 0) long startTime,
+ @IntRange(from = 0) long duration) {
+ this(path, startTime, duration, false);
+ }
+
+ /**
+ * @param path The path to follow. Must have exactly one contour. The bounds of the path
+ * must not be negative. The path must not be empty. If the path has zero length
+ * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time the stroke should start. Must not be negative.
+ * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+ * Must be positive.
+ * @param willContinue {@code true} if this stroke will be continued by one in the
+ * next gesture {@code false} otherwise. Continued strokes keep their pointers down when
+ * the gesture completes.
+ */
+ public StrokeDescription(@NonNull Path path,
+ @IntRange(from = 0) long startTime,
+ @IntRange(from = 0) long duration,
+ boolean willContinue) {
+ mContinued = willContinue;
+ Preconditions.checkArgument(duration > 0, "Duration must be positive");
+ Preconditions.checkArgument(startTime >= 0, "Start time must not be negative");
+ Preconditions.checkArgument(!path.isEmpty(), "Path is empty");
+ RectF bounds = new RectF();
+ path.computeBounds(bounds, false /* unused */);
+ Preconditions.checkArgument((bounds.bottom >= 0) && (bounds.top >= 0)
+ && (bounds.right >= 0) && (bounds.left >= 0),
+ "Path bounds must not be negative");
+ mPath = new Path(path);
+ mPathMeasure = new PathMeasure(path, false);
+ if (mPathMeasure.getLength() == 0) {
+ // Treat zero-length paths as taps
+ Path tempPath = new Path(path);
+ tempPath.lineTo(-1, -1);
+ mTapLocation = new float[2];
+ PathMeasure pathMeasure = new PathMeasure(tempPath, false);
+ pathMeasure.getPosTan(0, mTapLocation, null);
+ }
+ if (mPathMeasure.nextContour()) {
+ throw new IllegalArgumentException("Path has more than one contour");
+ }
+ /*
+ * Calling nextContour has moved mPathMeasure off the first contour, which is the only
+ * one we care about. Set the path again to go back to the first contour.
+ */
+ mPathMeasure.setPath(mPath, false);
+ mStartTime = startTime;
+ mEndTime = startTime + duration;
+ mTimeToLengthConversion = getLength() / duration;
+ mId = sIdCounter++;
+ }
+
+ /**
+ * Retrieve a copy of the path for this stroke
+ *
+ * @return A copy of the path
+ */
+ public Path getPath() {
+ return new Path(mPath);
+ }
+
+ /**
+ * Get the stroke's start time
+ *
+ * @return the start time for this stroke.
+ */
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ /**
+ * Get the stroke's duration
+ *
+ * @return the duration for this stroke
+ */
+ public long getDuration() {
+ return mEndTime - mStartTime;
+ }
+
+ /**
+ * Get the stroke's ID. The ID is used when a stroke is to be continued by another
+ * stroke in a future gesture.
+ *
+ * @return the ID of this stroke
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Create a new stroke that will continue this one. This is only possible if this stroke
+ * will continue.
+ *
+ * @param path The path for the stroke that continues this one. The starting point of
+ * this path must match the ending point of the stroke it continues.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time this stroke should start. Must not be negative. This time is from
+ * the start of the new gesture, not the one being continued.
+ * @param duration The duration for the new stroke. Must not be negative.
+ * @param willContinue {@code true} if this stroke will be continued by one in the
+ * next gesture {@code false} otherwise.
+ * @return
+ */
+ public StrokeDescription continueStroke(Path path, long startTime, long duration,
+ boolean willContinue) {
+ if (!mContinued) {
+ throw new IllegalStateException(
+ "Only strokes marked willContinue can be continued");
+ }
+ StrokeDescription strokeDescription =
+ new StrokeDescription(path, startTime, duration, willContinue);
+ strokeDescription.mContinuedStrokeId = mId;
+ return strokeDescription;
+ }
+
+ /**
+ * Check if this stroke is marked to continue in the next gesture.
+ *
+ * @return {@code true} if the stroke is to be continued.
+ */
+ public boolean willContinue() {
+ return mContinued;
+ }
+
+ /**
+ * Get the ID of the stroke that this one will continue.
+ *
+ * @return The ID of the stroke that this stroke continues, or 0 if no such stroke exists.
+ * @hide
+ */
+ public int getContinuedStrokeId() {
+ return mContinuedStrokeId;
+ }
+
+ float getLength() {
+ return mPathMeasure.getLength();
+ }
+
+ /* Assumes hasPointForTime returns true */
+ boolean getPosForTime(long time, float[] pos) {
+ if (mTapLocation != null) {
+ pos[0] = mTapLocation[0];
+ pos[1] = mTapLocation[1];
+ return true;
+ }
+ if (time == mEndTime) {
+ // Close to the end time, roundoff can be a problem
+ return mPathMeasure.getPosTan(getLength(), pos, null);
+ }
+ float length = mTimeToLengthConversion * ((float) (time - mStartTime));
+ return mPathMeasure.getPosTan(length, pos, null);
+ }
+
+ boolean hasPointForTime(long time) {
+ return ((time >= mStartTime) && (time <= mEndTime));
+ }
+ }
+
+ /**
+ * The location of a finger for gesture dispatch
+ *
+ * @hide
+ */
+ public static class TouchPoint implements Parcelable {
+ private static final int FLAG_IS_START_OF_PATH = 0x01;
+ private static final int FLAG_IS_END_OF_PATH = 0x02;
+
+ public int mStrokeId;
+ public int mContinuedStrokeId;
+ public boolean mIsStartOfPath;
+ public boolean mIsEndOfPath;
+ public float mX;
+ public float mY;
+
+ public TouchPoint() {
+ }
+
+ public TouchPoint(TouchPoint pointToCopy) {
+ copyFrom(pointToCopy);
+ }
+
+ public TouchPoint(Parcel parcel) {
+ mStrokeId = parcel.readInt();
+ mContinuedStrokeId = parcel.readInt();
+ int startEnd = parcel.readInt();
+ mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
+ mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
+ mX = parcel.readFloat();
+ mY = parcel.readFloat();
+ }
+
+ public void copyFrom(TouchPoint other) {
+ mStrokeId = other.mStrokeId;
+ mContinuedStrokeId = other.mContinuedStrokeId;
+ mIsStartOfPath = other.mIsStartOfPath;
+ mIsEndOfPath = other.mIsEndOfPath;
+ mX = other.mX;
+ mY = other.mY;
+ }
+
+ @Override
+ public String toString() {
+ return "TouchPoint{"
+ + "mStrokeId=" + mStrokeId
+ + ", mContinuedStrokeId=" + mContinuedStrokeId
+ + ", mIsStartOfPath=" + mIsStartOfPath
+ + ", mIsEndOfPath=" + mIsEndOfPath
+ + ", mX=" + mX
+ + ", mY=" + mY
+ + '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStrokeId);
+ dest.writeInt(mContinuedStrokeId);
+ int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
+ startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
+ dest.writeInt(startEnd);
+ dest.writeFloat(mX);
+ dest.writeFloat(mY);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<TouchPoint> CREATOR
+ = new Parcelable.Creator<TouchPoint>() {
+ public TouchPoint createFromParcel(Parcel in) {
+ return new TouchPoint(in);
+ }
+
+ public TouchPoint[] newArray(int size) {
+ return new TouchPoint[size];
+ }
+ };
+ }
+
+ /**
+ * A step along a gesture. Contains all of the touch points at a particular time
+ *
+ * @hide
+ */
+ public static class GestureStep implements Parcelable {
+ public long timeSinceGestureStart;
+ public int numTouchPoints;
+ public TouchPoint[] touchPoints;
+
+ public GestureStep(long timeSinceGestureStart, int numTouchPoints,
+ TouchPoint[] touchPointsToCopy) {
+ this.timeSinceGestureStart = timeSinceGestureStart;
+ this.numTouchPoints = numTouchPoints;
+ this.touchPoints = new TouchPoint[numTouchPoints];
+ for (int i = 0; i < numTouchPoints; i++) {
+ this.touchPoints[i] = new TouchPoint(touchPointsToCopy[i]);
+ }
+ }
+
+ public GestureStep(Parcel parcel) {
+ timeSinceGestureStart = parcel.readLong();
+ Parcelable[] parcelables =
+ parcel.readParcelableArray(TouchPoint.class.getClassLoader(), TouchPoint.class);
+ numTouchPoints = (parcelables == null) ? 0 : parcelables.length;
+ touchPoints = new TouchPoint[numTouchPoints];
+ for (int i = 0; i < numTouchPoints; i++) {
+ touchPoints[i] = (TouchPoint) parcelables[i];
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timeSinceGestureStart);
+ dest.writeParcelableArray(touchPoints, flags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<GestureStep> CREATOR
+ = new Parcelable.Creator<GestureStep>() {
+ public GestureStep createFromParcel(Parcel in) {
+ return new GestureStep(in);
+ }
+
+ public GestureStep[] newArray(int size) {
+ return new GestureStep[size];
+ }
+ };
+ }
+
+ /**
+ * Class to convert a GestureDescription to a series of GestureSteps.
+ *
+ * @hide
+ */
+ public static class MotionEventGenerator {
+ /* Lazily-created scratch memory for processing touches */
+ private static TouchPoint[] sCurrentTouchPoints;
+
+ public static List<GestureStep> getGestureStepsFromGestureDescription(
+ GestureDescription description, int sampleTimeMs) {
+ final List<GestureStep> gestureSteps = new ArrayList<>();
+
+ // Point data at each time we generate an event for
+ final TouchPoint[] currentTouchPoints =
+ getCurrentTouchPoints(description.getStrokeCount());
+ int currentTouchPointSize = 0;
+ /* Loop through each time slice where there are touch points */
+ long timeSinceGestureStart = 0;
+ long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart);
+ while (nextKeyPointTime >= 0) {
+ timeSinceGestureStart = (currentTouchPointSize == 0) ? nextKeyPointTime
+ : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs);
+ currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
+ currentTouchPoints);
+ gestureSteps.add(new GestureStep(timeSinceGestureStart, currentTouchPointSize,
+ currentTouchPoints));
+
+ /* Move to next time slice */
+ nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1);
+ }
+ return gestureSteps;
+ }
+
+ private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
+ if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
+ sCurrentTouchPoints = new TouchPoint[requiredCapacity];
+ for (int i = 0; i < requiredCapacity; i++) {
+ sCurrentTouchPoints[i] = new TouchPoint();
+ }
+ }
+ return sCurrentTouchPoints;
+ }
+ }
+}
diff --git a/android-34/android/accessibilityservice/InputMethod.java b/android-34/android/accessibilityservice/InputMethod.java
new file mode 100644
index 0000000..93888ef
--- /dev/null
+++ b/android-34/android/accessibilityservice/InputMethod.java
@@ -0,0 +1,530 @@
+/*
+ * 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.accessibilityservice;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextAttribute;
+
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+
+/**
+ * This class provides input method APIs. Some public methods such as
+ * @link #onUpdateSelection(int, int, int, int, int, int)} do nothing by default and service
+ * developers should override them as needed. Developers should also override
+ * {@link AccessibilityService#onCreateInputMethod()} to return
+ * their custom InputMethod implementation. Accessibility services also need to set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs.
+ */
+public class InputMethod {
+ private static final String LOG_TAG = "A11yInputMethod";
+
+ private final AccessibilityService mService;
+ private boolean mInputStarted;
+ private RemoteAccessibilityInputConnection mStartedInputConnection;
+ private EditorInfo mInputEditorInfo;
+
+ /**
+ * Creates a new InputMethod instance for the given <code>service</code>, so that the
+ * accessibility service can control editing.
+ */
+ public InputMethod(@NonNull AccessibilityService service) {
+ mService = service;
+ }
+
+ /**
+ * Retrieve the currently active InputConnection that is bound to
+ * the input method, or null if there is none.
+ */
+ @Nullable
+ public final AccessibilityInputConnection getCurrentInputConnection() {
+ if (mStartedInputConnection != null) {
+ return new AccessibilityInputConnection(mStartedInputConnection);
+ }
+ return null;
+ }
+
+ /**
+ * Whether the input has started.
+ */
+ public final boolean getCurrentInputStarted() {
+ return mInputStarted;
+ }
+
+ /**
+ * Get the EditorInfo which describes several attributes of a text editing object
+ * that an accessibility service is communicating with (typically an EditText).
+ */
+ @Nullable
+ public final EditorInfo getCurrentInputEditorInfo() {
+ return mInputEditorInfo;
+ }
+
+ /**
+ * Called to inform the accessibility service that text input has started in an
+ * editor. You should use this callback to initialize the state of your
+ * input to match the state of the editor given to it.
+ *
+ * @param attribute The attributes of the editor that input is starting
+ * in.
+ * @param restarting Set to true if input is restarting in the same
+ * editor such as because the application has changed the text in
+ * the editor. Otherwise will be false, indicating this is a new
+ * session with the editor.
+ */
+ public void onStartInput(@NonNull EditorInfo attribute, boolean restarting) {
+ // Intentionally empty
+ }
+
+ /**
+ * Called to inform the accessibility service that text input has finished in
+ * the last editor. At this point there may be a call to
+ * {@link #onStartInput(EditorInfo, boolean)} to perform input in a
+ * new editor, or the accessibility service may be left idle. This method is
+ * <em>not</em> called when input restarts in the same editor.
+ *
+ * <p>The default
+ * implementation uses the InputConnection to clear any active composing
+ * text; you can override this (not calling the base class implementation)
+ * to perform whatever behavior you would like.
+ */
+ public void onFinishInput() {
+ // Intentionally empty
+ }
+
+ /**
+ * Called when the application has reported a new selection region of
+ * the text. This is called whether or not the accessibility service has requested
+ * extracted text updates, although if so it will not receive this call
+ * if the extracted text has changed as well.
+ *
+ * <p>Be careful about changing the text in reaction to this call with
+ * methods such as setComposingText, commitText or
+ * deleteSurroundingText. If the cursor moves as a result, this method
+ * will be called again, which may result in an infinite loop.
+ */
+ public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+ int newSelEnd, int candidatesStart, int candidatesEnd) {
+ // Intentionally empty
+ }
+
+ final void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+ final AccessibilityInputMethodSessionWrapper wrapper =
+ new AccessibilityInputMethodSessionWrapper(mService.getMainLooper(),
+ new SessionImpl());
+ try {
+ callback.sessionCreated(wrapper, mService.getConnectionId());
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ final void startInput(@Nullable RemoteAccessibilityInputConnection ic,
+ @NonNull EditorInfo attribute) {
+ Log.v(LOG_TAG, "startInput(): editor=" + attribute);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.startInput");
+ doStartInput(ic, attribute, false /* restarting */);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void restartInput(@Nullable RemoteAccessibilityInputConnection ic,
+ @NonNull EditorInfo attribute) {
+ Log.v(LOG_TAG, "restartInput(): editor=" + attribute);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.restartInput");
+ doStartInput(ic, attribute, true /* restarting */);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+
+ final void doStartInput(RemoteAccessibilityInputConnection ic, EditorInfo attribute,
+ boolean restarting) {
+ if ((ic == null || !restarting) && mInputStarted) {
+ doFinishInput();
+ if (ic == null) {
+ // Unlike InputMethodService, A11y IME should not observe fallback InputConnection.
+ return;
+ }
+ }
+ mInputStarted = true;
+ mStartedInputConnection = ic;
+ mInputEditorInfo = attribute;
+ Log.v(LOG_TAG, "CALL: onStartInput");
+ onStartInput(attribute, restarting);
+ }
+
+ final void doFinishInput() {
+ Log.v(LOG_TAG, "CALL: doFinishInput");
+ if (mInputStarted) {
+ Log.v(LOG_TAG, "CALL: onFinishInput");
+ onFinishInput();
+ }
+ mInputStarted = false;
+ mStartedInputConnection = null;
+ mInputEditorInfo = null;
+ }
+
+ /**
+ * This class provides the allowed list of {@link InputConnection} APIs for
+ * accessibility services.
+ */
+ public final class AccessibilityInputConnection {
+ private final RemoteAccessibilityInputConnection mIc;
+ AccessibilityInputConnection(RemoteAccessibilityInputConnection ic) {
+ this.mIc = ic;
+ }
+
+ /**
+ * Commit text to the text box and set the new cursor position. This method is
+ * used to allow the IME to provide extra information while setting up text.
+ *
+ * <p>This method commits the contents of the currently composing text, and then
+ * moves the cursor according to {@code newCursorPosition}. If there
+ * is no composing text when this method is called, the new text is
+ * inserted at the cursor position, removing text inside the selection
+ * if any.
+ *
+ * <p>Calling this method will cause the editor to call
+ * {@link #onUpdateSelection(int, int, int, int,
+ * int, int)} on the current accessibility service after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the accessibility service by calling
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param text The text to commit. This may include styles.
+ * @param newCursorPosition The new cursor position around the text,
+ * in Java characters. If > 0, this is relative to the end
+ * of the text - 1; if <= 0, this is relative to the start
+ * of the text. So a value of 1 will always advance the cursor
+ * to the position after the full text being inserted. Note that
+ * this means you can't position the cursor within the text,
+ * because the editor can make modifications to the text
+ * you are providing so it is not possible to correctly specify
+ * locations there.
+ * @param textAttribute The extra information about the text.
+ */
+ public void commitText(@NonNull CharSequence text, int newCursorPosition,
+ @Nullable TextAttribute textAttribute) {
+ if (mIc != null) {
+ mIc.commitText(text, newCursorPosition, textAttribute);
+ }
+ }
+
+ /**
+ * Set the selection of the text editor. To set the cursor
+ * position, start and end should have the same value.
+ *
+ * <p>Since this moves the cursor, calling this method will cause
+ * the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int,
+ * int,int, int)} on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * <p>This has no effect on the composing region which must stay
+ * unchanged. The order of start and end is not important. In
+ * effect, the region from start to end and the region from end to
+ * start is the same. Editor authors, be ready to accept a start
+ * that is greater than end.</p>
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ */
+ public void setSelection(int start, int end) {
+ if (mIc != null) {
+ mIc.setSelection(start, end);
+ }
+ }
+
+ /**
+ * Gets the surrounding text around the current cursor, with <var>beforeLength</var>
+ * characters of text before the cursor (start of the selection), <var>afterLength</var>
+ * characters of text after the cursor (end of the selection), and all of the selected
+ * text. The range are for java characters, not glyphs that can be multiple characters.
+ *
+ * <p>This method may fail either if the input connection has become invalid (such as its
+ * process crashing), or the client is taking too long to respond with the text (it is
+ * given a couple seconds to return), or the protocol is not supported. In any of these
+ * cases, null is returned.
+ *
+ * <p>This method does not affect the text in the editor in any way, nor does it affect the
+ * selection or composing spans.</p>
+ *
+ * <p>If {@link InputConnection#GET_TEXT_WITH_STYLES} is supplied as flags, the editor
+ * should return a {@link android.text.Spanned} with all the spans set on the text.</p>
+ *
+ * <p><strong>Accessibility service authors:</strong> please consider this will trigger an
+ * IPC round-trip that will take some time. Assume this method consumes a lot of time.
+ *
+ * @param beforeLength The expected length of the text before the cursor.
+ * @param afterLength The expected length of the text after the cursor.
+ * @param flags Supplies additional options controlling how the text is returned. May be
+ * either {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+ * @return an {@link android.view.inputmethod.SurroundingText} object describing the
+ * surrounding text and state of selection, or null if the input connection is no longer
+ * valid, or the editor can't comply with the request for some reason, or the application
+ * does not implement this method. The length of the returned text might be less than the
+ * sum of <var>beforeLength</var> and <var>afterLength</var> .
+ * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is
+ * negative.
+ */
+ @Nullable
+ public SurroundingText getSurroundingText(
+ @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength,
+ @InputConnection.GetTextType int flags) {
+ if (mIc != null) {
+ return mIc.getSurroundingText(beforeLength, afterLength, flags);
+ }
+ return null;
+ }
+
+ /**
+ * Delete <var>beforeLength</var> characters of text before the
+ * current cursor position, and delete <var>afterLength</var>
+ * characters of text after the current cursor position, excluding
+ * the selection. Before and after refer to the order of the
+ * characters in the string, not to their visual representation:
+ * this means you don't have to figure out the direction of the
+ * text and can just use the indices as-is.
+ *
+ * <p>The lengths are supplied in Java chars, not in code points
+ * or in glyphs.</p>
+ *
+ * <p>Since this method only operates on text before and after the
+ * selection, it can't affect the contents of the selection. This
+ * may affect the composing span if the span includes characters
+ * that are to be deleted, but otherwise will not change it. If
+ * some characters in the composing span are deleted, the
+ * composing span will persist but get shortened by however many
+ * chars inside it have been removed.</p>
+ *
+ * <p><strong>Accessibility service authors:</strong> please be careful not to
+ * delete only half of a surrogate pair. Also take care not to
+ * delete more characters than are in the editor, as that may have
+ * ill effects on the application. Calling this method will cause
+ * the editor to call {@link InputMethod#onUpdateSelection(int, int, int, int, int, int)}
+ * on your service after the batch input is over.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text or change the selection position and use this
+ * method right away; you need to make sure the effects are
+ * consistent with the results of the latest edits. Also, although
+ * the IME should not send lengths bigger than the contents of the
+ * string, you should check the values for overflows and trim the
+ * indices to the size of the contents to avoid crashes. Since
+ * this changes the contents of the editor, you need to make the
+ * changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code
+ * unit. If this is greater than the number of existing characters between the
+ * beginning of the text and the cursor, then this method does not fail but deletes
+ * all the characters in that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters
+ * in that range.
+ */
+ public void deleteSurroundingText(int beforeLength, int afterLength) {
+ if (mIc != null) {
+ mIc.deleteSurroundingText(beforeLength, afterLength);
+ }
+ }
+
+ /**
+ * Send a key event to the process that is currently attached
+ * through this input connection. The event will be dispatched
+ * like a normal key event, to the currently focused view; this
+ * generally is the view that is providing this InputConnection,
+ * but due to the asynchronous nature of this protocol that can
+ * not be guaranteed and the focus may have changed by the time
+ * the event is received.
+ *
+ * <p>This method can be used to send key events to the
+ * application. For example, an on-screen keyboard may use this
+ * method to simulate a hardware keyboard. There are three types
+ * of standard keyboards, numeric (12-key), predictive (20-key)
+ * and ALPHA (QWERTY). You can specify the keyboard type by
+ * specify the device id of the key event.</p>
+ *
+ * <p>You will usually want to set the flag
+ * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+ * on all key event objects you give to this API; the flag will
+ * not be set for you.</p>
+ *
+ * <p>Note that it's discouraged to send such key events in normal
+ * operation; this is mainly for use with
+ * {@link android.text.InputType#TYPE_NULL} type text fields. Use
+ * the {@link #commitText} family of methods to send text to the
+ * application instead.</p>
+ *
+ * @param event The key event.
+ *
+ * @see KeyEvent
+ * @see KeyCharacterMap#NUMERIC
+ * @see KeyCharacterMap#PREDICTIVE
+ * @see KeyCharacterMap#ALPHA
+ */
+ public void sendKeyEvent(@NonNull KeyEvent event) {
+ if (mIc != null) {
+ mIc.sendKeyEvent(event);
+ }
+ }
+
+ /**
+ * Have the editor perform an action it has said it can do.
+ *
+ * @param editorAction This must be one of the action constants for
+ * {@link EditorInfo#imeOptions EditorInfo.imeOptions}, such as
+ * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}, or the value of
+ * {@link EditorInfo#actionId EditorInfo.actionId} if a custom action is available.
+ */
+ public void performEditorAction(int editorAction) {
+ if (mIc != null) {
+ mIc.performEditorAction(editorAction);
+ }
+ }
+
+ /**
+ * Perform a context menu action on the field. The given id may be one of:
+ * {@link android.R.id#selectAll},
+ * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+ * {@link android.R.id#cut}, {@link android.R.id#copy},
+ * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+ * or {@link android.R.id#switchInputMethod}
+ */
+ public void performContextMenuAction(int id) {
+ if (mIc != null) {
+ mIc.performContextMenuAction(id);
+ }
+ }
+
+ /**
+ * Retrieve the current capitalization mode in effect at the
+ * current cursor position in the text. See
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}
+ * for more information.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, 0 is returned.</p>
+ *
+ * <p>This method does not affect the text in the editor in any
+ * way, nor does it affect the selection or composing spans.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can change the
+ * cursor position and use this method right away; you need to make
+ * sure the returned value is consistent with the results of the
+ * latest edits and changes to the cursor position.</p>
+ *
+ * @param reqModes The desired modes to retrieve, as defined by
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+ * constants are defined so that you can simply pass the current
+ * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+ * directly in to here.
+ * @return the caps mode flags that are in effect at the current
+ * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
+ */
+ public int getCursorCapsMode(int reqModes) {
+ if (mIc != null) {
+ return mIc.getCursorCapsMode(reqModes);
+ }
+ return 0;
+ }
+
+ /**
+ * Clear the given meta key pressed states in the given input
+ * connection.
+ *
+ * <p>This can be used by the accessibility service to clear the meta key states set
+ * by a hardware keyboard with latched meta keys, if the editor
+ * keeps track of these.</p>
+ *
+ * @param states The states to be cleared, may be one or more bits as
+ * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+ */
+ public void clearMetaKeyStates(int states) {
+ if (mIc != null) {
+ mIc.clearMetaKeyStates(states);
+ }
+ }
+ }
+
+ /**
+ * Concrete implementation of {@link AccessibilityInputMethodSession} that provides all of the
+ * standard behavior for an A11y input method session.
+ */
+ private final class SessionImpl implements AccessibilityInputMethodSession {
+ boolean mEnabled = true;
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ @Override
+ public void finishInput() {
+ if (mEnabled) {
+ doFinishInput();
+ }
+ }
+
+ @Override
+ public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+ int newSelEnd, int candidatesStart, int candidatesEnd) {
+ if (mEnabled) {
+ InputMethod.this.onUpdateSelection(oldSelEnd, oldSelEnd, newSelStart,
+ newSelEnd, candidatesStart, candidatesEnd);
+ }
+ }
+
+ @Override
+ public void invalidateInput(EditorInfo editorInfo,
+ IRemoteAccessibilityInputConnection connection, int sessionId) {
+ if (!mEnabled || mStartedInputConnection == null
+ || !mStartedInputConnection.isSameConnection(connection)) {
+ // This is not an error, and can be safely ignored.
+ return;
+ }
+ editorInfo.makeCompatible(mService.getApplicationInfo().targetSdkVersion);
+ restartInput(new RemoteAccessibilityInputConnection(mStartedInputConnection, sessionId),
+ editorInfo);
+ }
+ }
+}
diff --git a/android-34/android/accessibilityservice/MagnificationConfig.java b/android-34/android/accessibilityservice/MagnificationConfig.java
new file mode 100644
index 0000000..5019aee
--- /dev/null
+++ b/android-34/android/accessibilityservice/MagnificationConfig.java
@@ -0,0 +1,286 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class describes the magnification config for {@link AccessibilityService} to control the
+ * magnification.
+ *
+ * <p>
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_DEFAULT},
+ * {@link AccessibilityService} will be able to control the activated magnifier on the display.
+ * If there is no magnifier activated, it controls the last-activated magnification mode.
+ * If there is no magnifier activated before, it controls full-screen magnifier by default.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_FULLSCREEN}.
+ * {@link AccessibilityService} will be able to control full-screen magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #MAGNIFICATION_MODE_WINDOW} and the platform
+ * supports {@link android.content.pm.PackageManager#FEATURE_WINDOW_MAGNIFICATION} feature.
+ * {@link AccessibilityService} will be able to control window magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * If the other magnification configs, scale centerX and centerY, are not set by the
+ * {@link Builder}, the configs should be current values or default values. And the center
+ * position ordinarily is the center of the screen.
+ * </p>
+ */
+public final class MagnificationConfig implements Parcelable {
+
+ /** The controlling magnification mode. It controls the activated magnifier. */
+ public static final int MAGNIFICATION_MODE_DEFAULT = 0;
+ /** The controlling magnification mode. It controls full-screen magnifier. */
+ public static final int MAGNIFICATION_MODE_FULLSCREEN = 1;
+ /**
+ * The controlling magnification mode. It is valid if the platform supports
+ * {@link android.content.pm.PackageManager#FEATURE_WINDOW_MAGNIFICATION} feature.
+ */
+ public static final int MAGNIFICATION_MODE_WINDOW = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = {
+ MAGNIFICATION_MODE_DEFAULT,
+ MAGNIFICATION_MODE_FULLSCREEN,
+ MAGNIFICATION_MODE_WINDOW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MagnificationMode {
+ }
+
+ private int mMode = MAGNIFICATION_MODE_DEFAULT;
+ private boolean mActivated = false;
+ private float mScale = Float.NaN;
+ private float mCenterX = Float.NaN;
+ private float mCenterY = Float.NaN;
+
+ private MagnificationConfig() {
+ /* do nothing */
+ }
+
+ private MagnificationConfig(@NonNull Parcel parcel) {
+ mMode = parcel.readInt();
+ mActivated = parcel.readBoolean();
+ mScale = parcel.readFloat();
+ mCenterX = parcel.readFloat();
+ mCenterY = parcel.readFloat();
+ }
+
+ /**
+ * Returns the magnification mode that is the current activated mode or the controlling mode of
+ * the config.
+ *
+ * @return The magnification mode
+ */
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Returns the activated state of the controlling magnifier. The controlling magnifier can be
+ * activated even if the scale returned by {@link MagnificationConfig#getScale()} equals to 1.0.
+ *
+ * @return {@code true} if the magnifier is activated and showing on screen,
+ * {@code false} otherwise.
+ */
+ public boolean isActivated() {
+ return mActivated;
+ }
+
+ /**
+ * Returns the magnification scale of the controlling magnifier
+ *
+ * @return The magnification scale
+ */
+ public float getScale() {
+ return mScale;
+ }
+
+ /**
+ * Returns the screen-relative X coordinate of the center of the magnification viewport.
+ *
+ * @return The X coordinate
+ */
+ public float getCenterX() {
+ return mCenterX;
+ }
+
+ /**
+ * Returns the screen-relative Y coordinate of the center of the magnification viewport.
+ *
+ * @return The Y coordinate
+ */
+ public float getCenterY() {
+ return mCenterY;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("MagnificationConfig[");
+ stringBuilder.append("mode: ").append(getMode());
+ stringBuilder.append(", ");
+ stringBuilder.append("activated: ").append(isActivated());
+ stringBuilder.append(", ");
+ stringBuilder.append("scale: ").append(getScale());
+ stringBuilder.append(", ");
+ stringBuilder.append("centerX: ").append(getCenterX());
+ stringBuilder.append(", ");
+ stringBuilder.append("centerY: ").append(getCenterY());
+ stringBuilder.append("] ");
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mMode);
+ parcel.writeBoolean(mActivated);
+ parcel.writeFloat(mScale);
+ parcel.writeFloat(mCenterX);
+ parcel.writeFloat(mCenterY);
+ }
+
+ /**
+ * Builder for creating {@link MagnificationConfig} objects.
+ */
+ public static final class Builder {
+
+ private int mMode = MAGNIFICATION_MODE_DEFAULT;
+ private boolean mActivated = true;
+ private float mScale = Float.NaN;
+ private float mCenterX = Float.NaN;
+ private float mCenterY = Float.NaN;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the magnification mode.
+ *
+ * @param mode The magnification mode
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setMode(@MagnificationMode int mode) {
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Sets magnification activated state.
+ *
+ * @param activated The magnification activated state
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setActivated(boolean activated) {
+ mActivated = activated;
+ return this;
+ }
+
+ /**
+ * Sets the magnification scale.
+ *
+ * @param scale The magnification scale, in the range [1, 8]
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setScale(@FloatRange(from = 1f, to = 8f) float scale) {
+ mScale = scale;
+ return this;
+ }
+
+ /**
+ * Sets the X coordinate of the center of the magnification viewport.
+ * The controlling magnifier will apply the given position.
+ *
+ * @param centerX the screen-relative X coordinate around which to
+ * center and scale that is in the range [0, screenWidth],
+ * or {@link Float#NaN} to leave unchanged
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setCenterX(float centerX) {
+ mCenterX = centerX;
+ return this;
+ }
+
+ /**
+ * Sets the Y coordinate of the center of the magnification viewport.
+ * The controlling magnifier will apply the given position.
+ *
+ * @param centerY the screen-relative Y coordinate around which to
+ * center and scale that is in the range [0, screenHeight],
+ * or {@link Float#NaN} to leave unchanged
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setCenterY(float centerY) {
+ mCenterY = centerY;
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link MagnificationConfig}
+ */
+ @NonNull
+ public MagnificationConfig build() {
+ MagnificationConfig magnificationConfig = new MagnificationConfig();
+ magnificationConfig.mMode = mMode;
+ magnificationConfig.mActivated = mActivated;
+ magnificationConfig.mScale = mScale;
+ magnificationConfig.mCenterX = mCenterX;
+ magnificationConfig.mCenterY = mCenterY;
+ return magnificationConfig;
+ }
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @NonNull Parcelable.Creator<MagnificationConfig> CREATOR =
+ new Parcelable.Creator<MagnificationConfig>() {
+ public MagnificationConfig createFromParcel(Parcel parcel) {
+ return new MagnificationConfig(parcel);
+ }
+
+ public MagnificationConfig[] newArray(int size) {
+ return new MagnificationConfig[size];
+ }
+ };
+}
diff --git a/android-34/android/accessibilityservice/TouchInteractionController.java b/android-34/android/accessibilityservice/TouchInteractionController.java
new file mode 100644
index 0000000..af00f31
--- /dev/null
+++ b/android-34/android/accessibilityservice/TouchInteractionController.java
@@ -0,0 +1,447 @@
+/*
+ * 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.accessibilityservice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+/**
+ * This class allows a service to handle touch exploration and the detection of specialized
+ * accessibility gestures. The service receives motion events and can match those motion events
+ * against the gestures it supports. The service can also request the framework enter three other
+ * states of operation for the duration of this interaction. Upon entering any of these states the
+ * framework will take over and the service will not receive motion events until the start of a new
+ * interaction. The states are as follows:
+ *
+ * <ul>
+ * <li>The service can tell the framework that this interaction is touch exploration. The user is
+ * trying to explore the screen rather than manipulate it. The framework will then convert the
+ * motion events to hover events to support touch exploration.
+ * <li>The service can tell the framework that this interaction is a dragging interaction where
+ * two fingers are used to execute a one-finger gesture such as scrolling the screen. The
+ * service must specify which of the two fingers should be passed through to rest of the input
+ * pipeline.
+ * <li>Finally, the service can request that the framework delegate this interaction, meaning pass
+ * it through to the rest of the input pipeline as-is.
+ * </ul>
+ *
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE } is enabled, this
+ * controller will receive all motion events received by the framework for the specified display
+ * when not touch-exploring or delegating. If the service classifies this interaction as touch
+ * exploration or delegating the framework will stop sending motion events to the service for the
+ * duration of this interaction. If the service classifies this interaction as a dragging
+ * interaction the framework will send motion events to the service to allow the service to
+ * determine if the interaction still qualifies as dragging or if it has become a delegating
+ * interaction. If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE } is disabled
+ * this controller will not receive any motion events because touch interactions are being passed
+ * through to the input pipeline unaltered.
+ * Note that {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE }
+ * requires setting {@link android.R.attr#canRequestTouchExplorationMode} as well.
+ */
+public final class TouchInteractionController {
+ /** The state where the user is not touching the screen. */
+ public static final int STATE_CLEAR = 0;
+ /**
+ * The state where the user is touching the screen and the service is receiving motion events.
+ */
+ public static final int STATE_TOUCH_INTERACTING = 1;
+ /**
+ * The state where the user is explicitly exploring the screen. The service is not receiving
+ * motion events.
+ */
+ public static final int STATE_TOUCH_EXPLORING = 2;
+ /**
+ * The state where the user is dragging with two fingers. The service is not receiving motion
+ * events. The selected finger is being dispatched to the rest of the input pipeline to execute
+ * the drag.
+ */
+ public static final int STATE_DRAGGING = 3;
+ /**
+ * The user is performing a gesture which is being passed through to the input pipeline as-is.
+ * The service is not receiving motion events.
+ */
+ public static final int STATE_DELEGATING = 4;
+
+ @IntDef({
+ STATE_CLEAR,
+ STATE_TOUCH_INTERACTING,
+ STATE_TOUCH_EXPLORING,
+ STATE_DRAGGING,
+ STATE_DELEGATING
+ })
+ private @interface State {}
+
+ // The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID
+ // in frameworks/native/include/input/Input.h)
+ private static final int MAX_POINTER_COUNT = 32;
+
+ private final AccessibilityService mService;
+ private final Object mLock;
+ private final int mDisplayId;
+ private boolean mServiceDetectsGestures;
+ /** Map of callbacks to executors. Lazily created when adding the first callback. */
+ private ArrayMap<Callback, Executor> mCallbacks;
+ // A list of motion events that should be queued until a pending transition has taken place.
+ private Queue<MotionEvent> mQueuedMotionEvents = new LinkedList<>();
+ // Whether this controller is waiting for a state transition.
+ // Motion events will be queued and sent to listeners after the transition has taken place.
+ private boolean mStateChangeRequested = false;
+
+ // The current state of the display.
+ private int mState = STATE_CLEAR;
+
+ TouchInteractionController(
+ @NonNull AccessibilityService service, @NonNull Object lock, int displayId) {
+ mDisplayId = displayId;
+ mLock = lock;
+ mService = service;
+ }
+
+ /**
+ * Adds the specified callback to the list of callbacks. The callback will
+ * run using on the specified {@link Executor}', or on the service's main thread if the
+ * Executor is {@code null}.
+ * @param callback the callback to add, must be non-null
+ * @param executor the executor for this callback, or {@code null} to execute on the service's
+ * main thread
+ */
+ public void registerCallback(@Nullable Executor executor, @NonNull Callback callback) {
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ mCallbacks = new ArrayMap<>();
+ }
+ mCallbacks.put(callback, executor);
+ if (mCallbacks.size() == 1) {
+ setServiceDetectsGestures(true);
+ }
+ }
+ }
+
+ /**
+ * Unregisters the specified callback.
+ *
+ * @param callback the callback to remove, must be non-null
+ * @return {@code true} if the callback was removed, {@code false} otherwise
+ */
+ public boolean unregisterCallback(@NonNull Callback callback) {
+ if (mCallbacks == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ boolean result = mCallbacks.remove(callback) != null;
+ if (result && mCallbacks.size() == 0) {
+ setServiceDetectsGestures(false);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Removes all callbacks and returns control of touch interactions to the framework.
+ */
+ public void unregisterAllCallbacks() {
+ if (mCallbacks != null) {
+ synchronized (mLock) {
+ mCallbacks.clear();
+ setServiceDetectsGestures(false);
+ }
+ }
+ }
+
+ /**
+ * Dispatches motion events to any registered callbacks. This should be called on the service's
+ * main thread.
+ */
+ void onMotionEvent(MotionEvent event) {
+ if (mStateChangeRequested) {
+ mQueuedMotionEvents.add(event);
+ } else {
+ sendEventToAllListeners(event);
+ }
+ }
+
+ private void sendEventToAllListeners(MotionEvent event) {
+ final ArrayMap<Callback, Executor> entries;
+ synchronized (mLock) {
+ // callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final Callback callback = entries.keyAt(i);
+ final Executor executor = entries.valueAt(i);
+ if (executor != null) {
+ executor.execute(() -> callback.onMotionEvent(event));
+ } else {
+ // We're already on the main thread, just run the callback.
+ callback.onMotionEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Dispatches motion events to any registered callbacks. This should be called on the service's
+ * main thread.
+ */
+ void onStateChanged(@State int state) {
+ mState = state;
+ final ArrayMap<Callback, Executor> entries;
+ synchronized (mLock) {
+ // callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final Callback callback = entries.keyAt(i);
+ final Executor executor = entries.valueAt(i);
+ if (executor != null) {
+ executor.execute(() -> callback.onStateChanged(state));
+ } else {
+ // We're already on the main thread, just run the callback.
+ callback.onStateChanged(state);
+ }
+ }
+ mStateChangeRequested = false;
+ while (mQueuedMotionEvents.size() > 0) {
+ sendEventToAllListeners(mQueuedMotionEvents.poll());
+ }
+ }
+
+ /**
+ * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+ * controller will receive all motion events received by the framework for the specified display
+ * when not touch-exploring, delegating, or dragging. This allows the service to detect its own
+ * gestures, and use its own logic to judge when the framework should start touch-exploring,
+ * delegating, or dragging. If {@link
+ * AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE } is disabled this flag has no
+ * effect.
+ *
+ * @see AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ */
+ private void setServiceDetectsGestures(boolean mode) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.setServiceDetectsGesturesEnabled(mDisplayId, mode);
+ mServiceDetectsGestures = mode;
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at
+ * least one callback has been added for this display this function tells the framework to
+ * initiate touch exploration. Touch exploration will continue for the duration of this
+ * interaction.
+ */
+ public void requestTouchExploration() {
+ validateTransitionRequest();
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.requestTouchExploration(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ mStateChangeRequested = true;
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to initiate a dragging
+ * interaction using the specified pointer. The pointer's movements will be passed through to
+ * the rest of the input pipeline. Dragging is often used to perform two-finger scrolling.
+ *
+ * @param pointerId the pointer to be passed through to the rest of the input pipeline. If the
+ * pointer id is valid but not actually present on the screen it will be ignored.
+ * @throws IllegalArgumentException if the pointer id is outside of the allowed range.
+ */
+ public void requestDragging(int pointerId) {
+ validateTransitionRequest();
+ if (pointerId < 0 || pointerId > MAX_POINTER_COUNT) {
+ throw new IllegalArgumentException("Invalid pointer id: " + pointerId);
+ }
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.requestDragging(mDisplayId, pointerId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ mStateChangeRequested = true;
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to initiate a delegating
+ * interaction. Motion events will be passed through as-is to the rest of the input pipeline for
+ * the duration of this interaction.
+ */
+ public void requestDelegating() {
+ validateTransitionRequest();
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.requestDelegating(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ mStateChangeRequested = true;
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to perform a click.
+ * The framework will first try to perform
+ * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_CLICK} on the item with
+ * accessibility focus. If that fails, the framework will simulate a click using motion events
+ * on the last location to have accessibility focus.
+ */
+ public void performClick() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.onDoubleTap(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * If {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} and {@link If
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled and at least
+ * one callback has been added, this function tells the framework to perform a long click.
+ * The framework will simulate a long click using motion events on the last location with
+ * accessibility focus and will delegate any movements to the rest of the input pipeline. This
+ * allows a user to double-tap and hold to trigger a drag and then execute that drag by moving
+ * their finger.
+ */
+ public void performLongClickAndStartDrag() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance()
+ .getConnection(mService.getConnectionId());
+ if (connection != null) {
+ try {
+ connection.onDoubleTapAndHold(mDisplayId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ private void validateTransitionRequest() {
+ if (!mServiceDetectsGestures || mCallbacks.size() == 0) {
+ throw new IllegalStateException(
+ "State transitions are not allowed without first adding a callback.");
+ }
+ if ((mState == STATE_DELEGATING || mState == STATE_TOUCH_EXPLORING)) {
+ throw new IllegalStateException(
+ "State transition requests are not allowed in " + stateToString(mState));
+ }
+ }
+
+ /** @return the maximum number of pointers that this display will accept. */
+ public int getMaxPointerCount() {
+ return MAX_POINTER_COUNT;
+ }
+
+ /** @return the display id associated with this controller. */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * @return the current state of this controller.
+ * @see TouchInteractionController#STATE_CLEAR
+ * @see TouchInteractionController#STATE_DELEGATING
+ * @see TouchInteractionController#STATE_DRAGGING
+ * @see TouchInteractionController#STATE_TOUCH_EXPLORING
+ */
+ public int getState() {
+ synchronized (mLock) {
+ return mState;
+ }
+ }
+
+ /** Returns a string representation of the specified state. */
+ @NonNull
+ public static String stateToString(int state) {
+ switch (state) {
+ case STATE_CLEAR:
+ return "STATE_CLEAR";
+ case STATE_TOUCH_INTERACTING:
+ return "STATE_TOUCH_INTERACTING";
+ case STATE_TOUCH_EXPLORING:
+ return "STATE_TOUCH_EXPLORING";
+ case STATE_DRAGGING:
+ return "STATE_DRAGGING";
+ case STATE_DELEGATING:
+ return "STATE_DELEGATING";
+ default:
+ return "Unknown state: " + state;
+ }
+ }
+
+ /** callbacks allow services to receive motion events and state change updates. */
+ public interface Callback {
+ /**
+ * Called when the framework has sent a motion event to the service.
+ *
+ * @param event the event being passed to the service.
+ */
+ void onMotionEvent(@NonNull MotionEvent event);
+
+ /**
+ * Called when the state of motion event dispatch for this display has changed.
+ *
+ * @param state the new state of motion event dispatch.
+ * @see TouchInteractionController#STATE_CLEAR
+ * @see TouchInteractionController#STATE_DELEGATING
+ * @see TouchInteractionController#STATE_DRAGGING
+ * @see TouchInteractionController#STATE_TOUCH_EXPLORING
+ */
+ void onStateChanged(@State int state);
+ }
+}
diff --git a/android-34/android/accessibilityservice/util/AccessibilityUtils.java b/android-34/android/accessibilityservice/util/AccessibilityUtils.java
new file mode 100644
index 0000000..fa32bb2
--- /dev/null
+++ b/android-34/android/accessibilityservice/util/AccessibilityUtils.java
@@ -0,0 +1,139 @@
+/*
+ * 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.accessibilityservice.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Collection of utilities for accessibility service.
+ *
+ * @hide
+ */
+public final class AccessibilityUtils {
+ private AccessibilityUtils() {}
+
+ // Used for html description of accessibility service. The <img> src tag must follow the
+ // prefix rule. e.g. <img src="R.drawable.fileName"/>
+ private static final String IMG_PREFIX = "R.drawable.";
+ private static final String ANCHOR_TAG = "a";
+ private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
+ Collections.singletonList(ANCHOR_TAG));
+
+ /**
+ * Gets the filtered html string for
+ * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+ * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
+ * the <img> tag which do not meet the custom specification and the <a> tag.
+ *
+ * @param text the target text is html format.
+ * @return the filtered html string.
+ */
+ public static @NonNull String getFilteredHtmlText(@NonNull String text) {
+ final String replacementStart = "<invalidtag ";
+ final String replacementEnd = "</invalidtag>";
+
+ for (String tag : UNSUPPORTED_TAG_LIST) {
+ final String regexStart = "(?i)<" + tag + "(\\s+|>)";
+ final String regexEnd = "(?i)</" + tag + "\\s*>";
+ text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
+ text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
+ }
+
+ final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
+ text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
+ replacementStart);
+
+ return text;
+ }
+
+ /**
+ * Loads the animated image for
+ * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+ * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
+ * whether to exceed the screen size.
+ *
+ * @param context the current context.
+ * @param applicationInfo the current application.
+ * @param resId the animated image resource id.
+ * @return the animated image which is safe.
+ */
+ @Nullable
+ public static Drawable loadSafeAnimatedImage(@NonNull Context context,
+ @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
+ if (resId == /* invalid */ 0) {
+ return null;
+ }
+
+ final PackageManager packageManager = context.getPackageManager();
+ final String packageName = applicationInfo.packageName;
+ final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
+ applicationInfo);
+ if (bannerDrawable == null) {
+ return null;
+ }
+
+ final boolean isImageWidthOverScreenLength =
+ bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
+ final boolean isImageHeightOverScreenLength =
+ bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
+
+ return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
+ ? null
+ : bannerDrawable;
+ }
+
+ /**
+ * Gets the width of the screen.
+ *
+ * @param context the current context.
+ * @return the width of the screen in term of pixels.
+ */
+ private static int getScreenWidthPixels(@NonNull Context context) {
+ final Resources resources = context.getResources();
+ final int screenWidthDp = resources.getConfiguration().screenWidthDp;
+
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
+ resources.getDisplayMetrics()));
+ }
+
+ /**
+ * Gets the height of the screen.
+ *
+ * @param context the current context.
+ * @return the height of the screen in term of pixels.
+ */
+ private static int getScreenHeightPixels(@NonNull Context context) {
+ final Resources resources = context.getResources();
+ final int screenHeightDp = resources.getConfiguration().screenHeightDp;
+
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
+ resources.getDisplayMetrics()));
+ }
+}
diff --git a/android-34/android/accounts/AbstractAccountAuthenticator.java b/android-34/android/accounts/AbstractAccountAuthenticator.java
new file mode 100644
index 0000000..45515dd
--- /dev/null
+++ b/android-34/android/accounts/AbstractAccountAuthenticator.java
@@ -0,0 +1,1014 @@
+/*
+ * 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.accounts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Abstract base class for creating AccountAuthenticators.
+ * In order to be an authenticator one must extend this class, provide implementations for the
+ * abstract methods, and write a service that returns the result of {@link #getIBinder()}
+ * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked
+ * with an intent with action {@link AccountManager#ACTION_AUTHENTICATOR_INTENT}. This service
+ * must specify the following intent filter and metadata tags in its AndroidManifest.xml file
+ * <pre>
+ * <intent-filter>
+ * <action android:name="android.accounts.AccountAuthenticator" />
+ * </intent-filter>
+ * <meta-data android:name="android.accounts.AccountAuthenticator"
+ * android:resource="@xml/authenticator" />
+ * </pre>
+ * The <code>android:resource</code> attribute must point to a resource that looks like:
+ * <pre>
+ * <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:accountType="typeOfAuthenticator"
+ * android:icon="@drawable/icon"
+ * android:smallIcon="@drawable/miniIcon"
+ * android:label="@string/label"
+ * android:accountPreferences="@xml/account_preferences"
+ * />
+ * </pre>
+ * Replace the icons and labels with your own resources. The <code>android:accountType</code>
+ * attribute must be a string that uniquely identifies your authenticator and will be the same
+ * string that user will use when making calls on the {@link AccountManager} and it also
+ * corresponds to {@link Account#type} for your accounts. One user of the android:icon is the
+ * "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's
+ * tab panels.
+ * <p>
+ * The preferences attribute points to a PreferenceScreen xml hierarchy that contains
+ * a list of PreferenceScreens that can be invoked to manage the authenticator. An example is:
+ * <pre>
+ * <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ * <PreferenceCategory android:title="@string/title_fmt" />
+ * <PreferenceScreen
+ * android:key="key1"
+ * android:title="@string/key1_action"
+ * android:summary="@string/key1_summary">
+ * <intent
+ * android:action="key1.ACTION"
+ * android:targetPackage="key1.package"
+ * android:targetClass="key1.class" />
+ * </PreferenceScreen>
+ * </PreferenceScreen>
+ * </pre>
+ *
+ * <p>
+ * The standard pattern for implementing any of the abstract methods is the following:
+ * <ul>
+ * <li> If the supplied arguments are enough for the authenticator to fully satisfy the request
+ * then it will do so and return a {@link Bundle} that contains the results.
+ * <li> If the authenticator needs information from the user to satisfy the request then it
+ * will create an {@link Intent} to an activity that will prompt the user for the information
+ * and then carry out the request. This intent must be returned in a Bundle as key
+ * {@link AccountManager#KEY_INTENT}.
+ * <p>
+ * The activity needs to return the final result when it is complete so the Intent should contain
+ * the {@link AccountAuthenticatorResponse} as
+ * {@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}.
+ * The activity must then call {@link AccountAuthenticatorResponse#onResult} or
+ * {@link AccountAuthenticatorResponse#onError} when it is complete.
+ * <li> If the authenticator cannot synchronously process the request and return a result then it
+ * may choose to return null and then use the AccountManagerResponse to send the result
+ * when it has completed the request. This asynchronous option is not available for the
+ * {@link #addAccount} method, which must complete synchronously.
+ * </ul>
+ * <p>
+ * The following descriptions of each of the abstract authenticator methods will not describe the
+ * possible asynchronous nature of the request handling and will instead just describe the input
+ * parameters and the expected result.
+ * <p>
+ * When writing an activity to satisfy these requests one must pass in the AccountManagerResponse
+ * and return the result via that response when the activity finishes (or whenever else the
+ * activity author deems it is the correct time to respond).
+ */
+public abstract class AbstractAccountAuthenticator {
+ private static final String TAG = "AccountAuthenticator";
+
+ /**
+ * Bundle key used for the {@code long} expiration time (in millis from the unix epoch) of the
+ * associated auth token.
+ *
+ * @see #getAuthToken
+ */
+ public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
+
+ /**
+ * Bundle key used for the {@link String} account type in session bundle.
+ * This is used in the default implementation of
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_AUTH_TOKEN_TYPE =
+ "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
+ /**
+ * Bundle key used for the {@link String} array of required features in
+ * session bundle. This is used in the default implementation of
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_REQUIRED_FEATURES =
+ "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
+ /**
+ * Bundle key used for the {@link Bundle} options in session bundle. This is
+ * used in default implementation of {@link #startAddAccountSession} and
+ * {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_OPTIONS =
+ "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
+ /**
+ * Bundle key used for the {@link Account} account in session bundle. This is used
+ * used in default implementation of {@link #startUpdateCredentialsSession}.
+ */
+ private static final String KEY_ACCOUNT =
+ "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
+
+ private final Context mContext;
+
+ public AbstractAccountAuthenticator(Context context) {
+ mContext = context;
+ }
+
+ private class Transport extends IAccountAuthenticator.Stub {
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void addAccount(IAccountAuthenticatorResponse response, String accountType,
+ String authTokenType, String[] features, Bundle options)
+ throws RemoteException {
+ super.addAccount_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount: accountType " + accountType
+ + ", authTokenType " + authTokenType
+ + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.addAccount(
+ new AccountAuthenticatorResponse(response),
+ accountType, authTokenType, features, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ } else {
+ response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle returned");
+ }
+ } catch (Exception e) {
+ handleException(response, "addAccount", accountType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void confirmCredentials(IAccountAuthenticatorResponse response,
+ Account account, Bundle options) throws RemoteException {
+ super.confirmCredentials_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "confirmCredentials: " + account);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials(
+ new AccountAuthenticatorResponse(response), account, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "confirmCredentials: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "confirmCredentials", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
+ String authTokenType)
+ throws RemoteException {
+ super.getAuthTokenLabel_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
+ }
+ try {
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL,
+ AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "getAuthTokenLabel: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ response.onResult(result);
+ } catch (Exception e) {
+ handleException(response, "getAuthTokenLabel", authTokenType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAuthToken(IAccountAuthenticatorResponse response,
+ Account account, String authTokenType, Bundle loginOptions)
+ throws RemoteException {
+ super.getAuthToken_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthToken: " + account
+ + ", authTokenType " + authTokenType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
+ new AccountAuthenticatorResponse(response), account,
+ authTokenType, loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAuthToken",
+ account.toString() + "," + authTokenType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle loginOptions) throws RemoteException {
+ super.updateCredentials_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updateCredentials: " + account
+ + ", authTokenType " + authTokenType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.updateCredentials(
+ new AccountAuthenticatorResponse(response), account,
+ authTokenType, loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ // Result may be null.
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "updateCredentials: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "updateCredentials",
+ account.toString() + "," + authTokenType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void editProperties(IAccountAuthenticatorResponse response,
+ String accountType) throws RemoteException {
+ super.editProperties_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.editProperties(
+ new AccountAuthenticatorResponse(response), accountType);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "editProperties", accountType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void hasFeatures(IAccountAuthenticatorResponse response,
+ Account account, String[] features) throws RemoteException {
+ super.hasFeatures_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.hasFeatures(
+ new AccountAuthenticatorResponse(response), account, features);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "hasFeatures", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
+ Account account) throws RemoteException {
+ super.getAccountRemovalAllowed_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
+ new AccountAuthenticatorResponse(response), account);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAccountRemovalAllowed", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
+ Account account) throws RemoteException {
+ super.getAccountCredentialsForCloning_enforcePermission();
+
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
+ new AccountAuthenticatorResponse(response), account);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAccountCredentialsForCloning", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws RemoteException {
+ super.addAccountFromCredentials_enforcePermission();
+
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.addAccountFromCredentials(
+ new AccountAuthenticatorResponse(response), account,
+ accountCredentials);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "addAccountFromCredentials", account.toString(), e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void startAddAccountSession(IAccountAuthenticatorResponse response,
+ String accountType, String authTokenType, String[] features, Bundle options)
+ throws RemoteException {
+ super.startAddAccountSession_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "startAddAccountSession: accountType " + accountType
+ + ", authTokenType " + authTokenType
+ + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession(
+ new AccountAuthenticatorResponse(response), accountType, authTokenType,
+ features, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "startAddAccountSession: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "startAddAccountSession", accountType, e);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void startUpdateCredentialsSession(
+ IAccountAuthenticatorResponse response,
+ Account account,
+ String authTokenType,
+ Bundle loginOptions) throws RemoteException {
+ super.startUpdateCredentialsSession_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "startUpdateCredentialsSession: "
+ + account
+ + ", authTokenType "
+ + authTokenType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this
+ .startUpdateCredentialsSession(
+ new AccountAuthenticatorResponse(response),
+ account,
+ authTokenType,
+ loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ // Result may be null.
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "startUpdateCredentialsSession: result "
+ + AccountManager.sanitizeResult(result));
+
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "startUpdateCredentialsSession",
+ account.toString() + "," + authTokenType, e);
+
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void finishSession(
+ IAccountAuthenticatorResponse response,
+ String accountType,
+ Bundle sessionBundle) throws RemoteException {
+ super.finishSession_enforcePermission();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "finishSession: accountType " + accountType);
+ }
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.finishSession(
+ new AccountAuthenticatorResponse(response), accountType, sessionBundle);
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "finishSession", accountType, e);
+
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCOUNT_MANAGER)
+ @Override
+ public void isCredentialsUpdateSuggested(
+ IAccountAuthenticatorResponse response,
+ Account account,
+ String statusToken) throws RemoteException {
+ super.isCredentialsUpdateSuggested_enforcePermission();
+
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this
+ .isCredentialsUpdateSuggested(
+ new AccountAuthenticatorResponse(response), account, statusToken);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "isCredentialsUpdateSuggested", account.toString(), e);
+ }
+ }
+ }
+
+ private void handleException(IAccountAuthenticatorResponse response, String method,
+ String data, Exception e) throws RemoteException {
+ if (e instanceof NetworkErrorException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
+ } else if (e instanceof UnsupportedOperationException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
+ method + " not supported");
+ } else if (e instanceof IllegalArgumentException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
+ method + " not supported");
+ } else {
+ Log.w(TAG, method + "(" + data + ")", e);
+ response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ method + " failed");
+ }
+ }
+
+ private Transport mTransport = new Transport();
+
+ /**
+ * @return the IBinder for the AccountAuthenticator
+ */
+ public final IBinder getIBinder() {
+ return mTransport.asBinder();
+ }
+
+ /**
+ * Returns a Bundle that contains the Intent of the activity that can be used to edit the
+ * properties. In order to indicate success the activity should call response.setResult()
+ * with a non-null Bundle.
+ * @param response used to set the result for the request. If the Constants.INTENT_KEY
+ * is set in the bundle then this response field is to be used for sending future
+ * results if and when the Intent is started.
+ * @param accountType the AccountType whose properties are to be edited.
+ * @return a Bundle containing the result or the Intent to start to continue the request.
+ * If this is null then the request is considered to still be active and the result should
+ * sent later using response.
+ */
+ public abstract Bundle editProperties(AccountAuthenticatorResponse response,
+ String accountType);
+
+ /**
+ * Adds an account of the specified accountType.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param accountType the type of account to add, will never be null
+ * @param authTokenType the type of auth token to retrieve after adding the account, may be null
+ * @param requiredFeatures a String array of authenticator-specific features that the added
+ * account must support, may be null
+ * @param options a Bundle of authenticator-specific options. It always contains
+ * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID}
+ * fields which will let authenticator know the identity of the caller.
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
+ * the account that was added, or
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+ String authTokenType, String[] requiredFeatures, Bundle options)
+ throws NetworkErrorException;
+
+ /**
+ * Checks that the user knows the credentials of an account.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be checked, will never be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response,
+ Account account, Bundle options)
+ throws NetworkErrorException;
+
+ /**
+ * Gets an authtoken for an account.
+ *
+ * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys
+ * depending on whether a token was successfully issued and, if not, whether one
+ * could be issued via some {@link android.app.Activity}.
+ * <p>
+ * If a token cannot be provided without some additional activity, the Bundle should contain
+ * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if
+ * there is no such activity, then a Bundle containing
+ * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be
+ * returned.
+ * <p>
+ * If a token can be successfully issued, the implementation should return the
+ * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the
+ * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In
+ * addition {@link AbstractAccountAuthenticator} implementations that declare themselves
+ * {@code android:customTokens=true} may also provide a non-negative {@link
+ * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration
+ * time (in millis since the unix epoch), tokens will be cached in memory based on
+ * application's packageName/signature for however long that was specified.
+ * <p>
+ * Implementers should assume that tokens will be cached on the basis of account and
+ * authTokenType. The system may ignore the contents of the supplied options Bundle when
+ * determining to re-use a cached token. Furthermore, implementers should assume a supplied
+ * expiration time will be treated as non-binding advice.
+ * <p>
+ * Finally, note that for {@code android:customTokens=false} authenticators, tokens are cached
+ * indefinitely until some client calls {@link
+ * AccountManager#invalidateAuthToken(String,String)}.
+ *
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be retrieved, will never be null
+ * @param authTokenType the type of auth token to retrieve, will never be null
+ * @param options a Bundle of authenticator-specific options. It always contains
+ * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID}
+ * fields which will let authenticator know the identity of the caller.
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle getAuthToken(AccountAuthenticatorResponse response,
+ Account account, String authTokenType, Bundle options)
+ throws NetworkErrorException;
+
+ /**
+ * Ask the authenticator for a localized label for the given authTokenType.
+ * @param authTokenType the authTokenType whose label is to be returned, will never be null
+ * @return the localized label of the auth token type, may be null if the type isn't known
+ */
+ public abstract String getAuthTokenLabel(String authTokenType);
+
+ /**
+ * Update the locally stored credentials for an account.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account whose credentials are to be updated, will never be null
+ * @param authTokenType the type of auth token to retrieve after updating the credentials,
+ * may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
+ * the account whose credentials were updated, or
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
+ Account account, String authTokenType, Bundle options) throws NetworkErrorException;
+
+ /**
+ * Checks if the account supports all the specified authenticator specific features.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to check, will never be null
+ * @param features an array of features to check, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features,
+ * false otherwise
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
+ Account account, String[] features) throws NetworkErrorException;
+
+ /**
+ * Checks if the removal of this account is allowed.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to check, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li> {@link AccountManager#KEY_INTENT}, or
+ * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is
+ * allowed, false otherwise
+ * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
+ * indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
+ Account account) throws NetworkErrorException {
+ final Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ return result;
+ }
+
+ /**
+ * Returns a Bundle that contains whatever is required to clone the account on a different
+ * user. The Bundle is passed to the authenticator instance in the target user via
+ * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}.
+ * The default implementation returns null, indicating that cloning is not supported.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)
+ */
+ public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
+ final Account account) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
+
+ /**
+ * Creates an account based on credentials provided by the authenticator instance of another
+ * user on the device, who has chosen to share the account with this user.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @param accountCredentials the Bundle containing the required credentials to create the
+ * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is
+ * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)
+ */
+ public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
+
+ /**
+ * Starts the add account session to authenticate user to an account of the
+ * specified accountType. No file I/O should be performed in this call.
+ * Account should be added to device only when {@link #finishSession} is
+ * called after this.
+ * <p>
+ * Note: when overriding this method, {@link #finishSession} should be
+ * overridden too.
+ * </p>
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param accountType the type of account to authenticate with, will never
+ * be null
+ * @param authTokenType the type of auth token to retrieve after
+ * authenticating with the account, may be null
+ * @param requiredFeatures a String array of authenticator-specific features
+ * that the account authenticated with must support, may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding
+ * the account to device later, and if account is authenticated,
+ * optional {@link AccountManager#KEY_PASSWORD} and
+ * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
+ * status of the account, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the
+ * request due to a network error
+ * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
+ */
+ public Bundle startAddAccountSession(
+ final AccountAuthenticatorResponse response,
+ final String accountType,
+ final String authTokenType,
+ final String[] requiredFeatures,
+ final Bundle options)
+ throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+ sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures);
+ sessionBundle.putBundle(KEY_OPTIONS, options);
+ Bundle result = new Bundle();
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+ response.onResult(result);
+ }
+
+ }).start();
+ return null;
+ }
+
+ /**
+ * Asks user to re-authenticate for an account but defers updating the
+ * locally stored credentials. No file I/O should be performed in this call.
+ * Local credentials should be updated only when {@link #finishSession} is
+ * called after this.
+ * <p>
+ * Note: when overriding this method, {@link #finishSession} should be
+ * overridden too.
+ * </p>
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param account the account whose credentials are to be updated, will
+ * never be null
+ * @param authTokenType the type of auth token to retrieve after updating
+ * the credentials, may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for
+ * updating the locally stored credentials later, and if account is
+ * re-authenticated, optional {@link AccountManager#KEY_PASSWORD}
+ * and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
+ * the status of the account later, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the
+ * request due to a network error
+ * @see #finishSession(AccountAuthenticatorResponse, String, Bundle)
+ */
+ public Bundle startUpdateCredentialsSession(
+ final AccountAuthenticatorResponse response,
+ final Account account,
+ final String authTokenType,
+ final Bundle options) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+ sessionBundle.putParcelable(KEY_ACCOUNT, account);
+ sessionBundle.putBundle(KEY_OPTIONS, options);
+ Bundle result = new Bundle();
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+ response.onResult(result);
+ }
+
+ }).start();
+ return null;
+ }
+
+ /**
+ * Finishes the session started by #startAddAccountSession or
+ * #startUpdateCredentials by installing the account to device with
+ * AccountManager, or updating the local credentials. File I/O may be
+ * performed in this call.
+ * <p>
+ * Note: when overriding this method, {@link #startAddAccountSession} and
+ * {@link #startUpdateCredentialsSession} should be overridden too.
+ * </p>
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param accountType the type of account to authenticate with, will never
+ * be null
+ * @param sessionBundle a bundle of session data created by
+ * {@link #startAddAccountSession} used for adding account to
+ * device, or by {@link #startUpdateCredentialsSession} used for
+ * updating local credentials.
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_NAME} and
+ * {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was
+ * added or local credentials were updated, and optional
+ * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking
+ * the status of the account later, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ * @see #startAddAccountSession and #startUpdateCredentialsSession
+ */
+ public Bundle finishSession(
+ final AccountAuthenticatorResponse response,
+ final String accountType,
+ final Bundle sessionBundle) throws NetworkErrorException {
+ if (TextUtils.isEmpty(accountType)) {
+ Log.e(TAG, "Account type cannot be empty.");
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE,
+ "accountType cannot be empty.");
+ return result;
+ }
+
+ if (sessionBundle == null) {
+ Log.e(TAG, "Session bundle cannot be null.");
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE,
+ "sessionBundle cannot be null.");
+ return result;
+ }
+
+ if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) {
+ // We cannot handle Session bundle not created by default startAddAccountSession(...)
+ // nor startUpdateCredentialsSession(...) implementation. Return error.
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE,
+ AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE,
+ "Authenticator must override finishSession if startAddAccountSession"
+ + " or startUpdateCredentialsSession is overridden.");
+ response.onResult(result);
+ return result;
+ }
+ String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE);
+ Bundle options = sessionBundle.getBundle(KEY_OPTIONS);
+ String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES);
+ Account account = sessionBundle.getParcelable(KEY_ACCOUNT, android.accounts.Account.class);
+ boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT);
+
+ // Actual options passed to add account or update credentials flow.
+ Bundle sessionOptions = new Bundle(sessionBundle);
+ // Remove redundant extras in session bundle before passing it to addAccount(...) or
+ // updateCredentials(...).
+ sessionOptions.remove(KEY_AUTH_TOKEN_TYPE);
+ sessionOptions.remove(KEY_REQUIRED_FEATURES);
+ sessionOptions.remove(KEY_OPTIONS);
+ sessionOptions.remove(KEY_ACCOUNT);
+
+ if (options != null) {
+ // options may contains old system info such as
+ // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update
+ // credentials flow, we should replace with the new values of the current call added
+ // to sessionBundle by AccountManager or AccountManagerService.
+ options.putAll(sessionOptions);
+ sessionOptions = options;
+ }
+
+ // Session bundle created by startUpdateCredentialsSession default implementation should
+ // contain KEY_ACCOUNT.
+ if (containsKeyAccount) {
+ return updateCredentials(response, account, authTokenType, options);
+ }
+ // Otherwise, session bundle was created by startAddAccountSession default implementation.
+ return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions);
+ }
+
+ /**
+ * Checks if update of the account credentials is suggested.
+ *
+ * @param response to send the result back to the AccountManager, will never be null.
+ * @param account the account to check, will never be null
+ * @param statusToken a String of token which can be used to check the status of locally
+ * stored credentials and if update of credentials is suggested
+ * @return a Bundle result or null if the result is to be returned via the response. The result
+ * will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_BOOLEAN_RESULT}, true if update of account's
+ * credentials is suggested, false otherwise
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the request due to a
+ * network error
+ */
+ public Bundle isCredentialsUpdateSuggested(
+ final AccountAuthenticatorResponse response,
+ Account account,
+ String statusToken) throws NetworkErrorException {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ return result;
+ }
+}
diff --git a/android-34/android/accounts/Account.java b/android-34/android/accounts/Account.java
new file mode 100644
index 0000000..0d6a079
--- /dev/null
+++ b/android-34/android/accounts/Account.java
@@ -0,0 +1,180 @@
+/*
+ * 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.accounts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Set;
+
+/**
+ * Value type that represents an Account in the {@link AccountManager}. This object is
+ * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it
+ * suitable for use as the key of a {@link java.util.Map}
+ */
+public class Account implements Parcelable {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private static final String TAG = "Account";
+
+ @GuardedBy("sAccessedAccounts")
+ private static final Set<Account> sAccessedAccounts = new ArraySet<>();
+
+ public final String name;
+ public final String type;
+ private String mSafeName;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private final @Nullable String accessId;
+
+ public boolean equals(@Nullable Object o) {
+ if (o == this) return true;
+ if (!(o instanceof Account)) return false;
+ final Account other = (Account)o;
+ return name.equals(other.name) && type.equals(other.type);
+ }
+
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + name.hashCode();
+ result = 31 * result + type.hashCode();
+ return result;
+ }
+
+ public Account(String name, String type) {
+ this(name, type, null);
+ }
+
+ /**
+ * @hide
+ */
+ public Account(@NonNull Account other, @NonNull String accessId) {
+ this(other.name, other.type, accessId);
+ }
+
+ /**
+ * @hide
+ */
+ public Account(String name, String type, String accessId) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("the name must not be empty: " + name);
+ }
+ if (TextUtils.isEmpty(type)) {
+ throw new IllegalArgumentException("the type must not be empty: " + type);
+ }
+ this.name = name;
+ this.type = type;
+ this.accessId = accessId;
+ }
+
+ public Account(Parcel in) {
+ this.name = in.readString();
+ this.type = in.readString();
+ if (TextUtils.isEmpty(name)) {
+ throw new android.os.BadParcelableException("the name must not be empty: " + name);
+ }
+ if (TextUtils.isEmpty(type)) {
+ throw new android.os.BadParcelableException("the type must not be empty: " + type);
+ }
+ this.accessId = in.readString();
+ if (accessId != null) {
+ synchronized (sAccessedAccounts) {
+ if (sAccessedAccounts.add(this)) {
+ try {
+ IAccountManager accountManager = IAccountManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCOUNT_SERVICE));
+ accountManager.onAccountAccessed(accessId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error noting account access", e);
+ }
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ public String getAccessId() {
+ return accessId;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name);
+ dest.writeString(type);
+ dest.writeString(accessId);
+ }
+
+ public static final @android.annotation.NonNull Creator<Account> CREATOR = new Creator<Account>() {
+ public Account createFromParcel(Parcel source) {
+ return new Account(source);
+ }
+
+ public Account[] newArray(int size) {
+ return new Account[size];
+ }
+ };
+
+ public String toString() {
+ return "Account {name=" + name + ", type=" + type + "}";
+ }
+
+ /**
+ * Return a string representation of the account that is safe to print
+ * to logs and other places where PII should be avoided.
+ * @hide
+ */
+ public String toSafeString() {
+ if (mSafeName == null) {
+ mSafeName = toSafeName(name, 'x');
+ }
+ return "Account {name=" + mSafeName + ", type=" + type + "}";
+ }
+
+ /**
+ * Given a name, replace all letter or digits with the replacement char.
+ * @param name The input name string.
+ * @param replacement the replacement character.
+ * @return the string after replacement.
+ * @hide
+ */
+ public static String toSafeName(String name, char replacement) {
+ final StringBuilder builder = new StringBuilder(64);
+ final int len = name.length();
+ for (int i = 0; i < len; i++) {
+ final char c = name.charAt(i);
+ if (Character.isLetterOrDigit(c)) {
+ builder.append(replacement);
+ } else {
+ builder.append(c);
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/android-34/android/accounts/AccountAndUser.java b/android-34/android/accounts/AccountAndUser.java
new file mode 100644
index 0000000..adbc4e9
--- /dev/null
+++ b/android-34/android/accounts/AccountAndUser.java
@@ -0,0 +1,55 @@
+/*
+ * 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.accounts;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Used to store the Account and the UserId this account is associated with.
+ *
+ * @hide
+ */
+public class AccountAndUser {
+ @UnsupportedAppUsage
+ public Account account;
+ @UnsupportedAppUsage
+ public int userId;
+
+ @UnsupportedAppUsage
+ public AccountAndUser(Account account, int userId) {
+ this.account = account;
+ this.userId = userId;
+ }
+
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AccountAndUser)) return false;
+ final AccountAndUser other = (AccountAndUser) o;
+ return this.account.equals(other.account)
+ && this.userId == other.userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return account.hashCode() + userId;
+ }
+
+ public String toString() {
+ return account.toString() + " u" + userId;
+ }
+}
diff --git a/android-34/android/accounts/AccountAuthenticatorActivity.java b/android-34/android/accounts/AccountAuthenticatorActivity.java
new file mode 100644
index 0000000..5f620f3
--- /dev/null
+++ b/android-34/android/accounts/AccountAuthenticatorActivity.java
@@ -0,0 +1,86 @@
+/*
+ * 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.accounts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Base class for implementing an Activity that is used to help implement an
+ * AbstractAccountAuthenticator. If the AbstractAccountAuthenticator needs to use an activity
+ * to handle the request then it can have the activity extend AccountAuthenticatorActivity.
+ * The AbstractAccountAuthenticator passes in the response to the intent using the following:
+ * <pre>
+ * intent.putExtra({@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
+ * </pre>
+ * The activity then sets the result that is to be handed to the response via
+ * {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
+ * This result will be sent as the result of the request when the activity finishes. If this
+ * is never set or if it is set to null then error {@link AccountManager#ERROR_CODE_CANCELED}
+ * will be called on the response.
+ *
+ * @deprecated Applications should extend Activity themselves. This class is not compatible with
+ * AppCompat, and the functionality it provides is not complex.
+ */
+@Deprecated
+public class AccountAuthenticatorActivity extends Activity {
+ private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null;
+ private Bundle mResultBundle = null;
+
+ /**
+ * Set the result that is to be sent as the result of the request that caused this
+ * Activity to be launched. If result is null or this method is never called then
+ * the request will be canceled.
+ * @param result this is returned as the result of the AbstractAccountAuthenticator request
+ */
+ public final void setAccountAuthenticatorResult(Bundle result) {
+ mResultBundle = result;
+ }
+
+ /**
+ * Retrieves the AccountAuthenticatorResponse from either the intent of the icicle, if the
+ * icicle is non-zero.
+ * @param icicle the save instance data of this Activity, may be null
+ */
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mAccountAuthenticatorResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, android.accounts.AccountAuthenticatorResponse.class);
+
+ if (mAccountAuthenticatorResponse != null) {
+ mAccountAuthenticatorResponse.onRequestContinued();
+ }
+ }
+
+ /**
+ * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ */
+ public void finish() {
+ if (mAccountAuthenticatorResponse != null) {
+ // send the result bundle back if set, otherwise send an error.
+ if (mResultBundle != null) {
+ mAccountAuthenticatorResponse.onResult(mResultBundle);
+ } else {
+ mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED,
+ "canceled");
+ }
+ mAccountAuthenticatorResponse = null;
+ }
+ super.finish();
+ }
+}
diff --git a/android-34/android/accounts/AccountAuthenticatorResponse.java b/android-34/android/accounts/AccountAuthenticatorResponse.java
new file mode 100644
index 0000000..a2a5799
--- /dev/null
+++ b/android-34/android/accounts/AccountAuthenticatorResponse.java
@@ -0,0 +1,100 @@
+/*
+ * 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.accounts;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Object used to communicate responses back to the AccountManager
+ */
+public class AccountAuthenticatorResponse implements Parcelable {
+ private static final String TAG = "AccountAuthenticator";
+
+ private IAccountAuthenticatorResponse mAccountAuthenticatorResponse;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
+ mAccountAuthenticatorResponse = response;
+ }
+
+ public AccountAuthenticatorResponse(Parcel parcel) {
+ mAccountAuthenticatorResponse =
+ IAccountAuthenticatorResponse.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ public void onResult(Bundle result) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+ + AccountManager.sanitizeResult(result));
+ }
+ try {
+ mAccountAuthenticatorResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onRequestContinued() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "AccountAuthenticatorResponse.onRequestContinued");
+ }
+ try {
+ mAccountAuthenticatorResponse.onRequestContinued();
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "AccountAuthenticatorResponse.onError: " + errorCode + ", " + errorMessage);
+ }
+ try {
+ mAccountAuthenticatorResponse.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mAccountAuthenticatorResponse.asBinder());
+ }
+
+ public static final @android.annotation.NonNull Creator<AccountAuthenticatorResponse> CREATOR =
+ new Creator<AccountAuthenticatorResponse>() {
+ public AccountAuthenticatorResponse createFromParcel(Parcel source) {
+ return new AccountAuthenticatorResponse(source);
+ }
+
+ public AccountAuthenticatorResponse[] newArray(int size) {
+ return new AccountAuthenticatorResponse[size];
+ }
+ };
+}
diff --git a/android-34/android/accounts/AccountManager.java b/android-34/android/accounts/AccountManager.java
new file mode 100644
index 0000000..821a23c
--- /dev/null
+++ b/android-34/android/accounts/AccountManager.java
@@ -0,0 +1,3485 @@
+/*
+ * 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.accounts;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
+import android.annotation.BroadcastBehavior;
+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.Size;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
+import android.app.Activity;
+import android.app.PropertyInvalidatedCache;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.UserPackage;
+import android.content.res.Resources;
+import android.database.SQLException;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import com.google.android.collect.Maps;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This class provides access to a centralized registry of the user's
+ * online accounts. The user enters credentials (username and password) once
+ * per account, granting applications access to online resources with
+ * "one-click" approval.
+ *
+ * <p>Different online services have different ways of handling accounts and
+ * authentication, so the account manager uses pluggable <em>authenticator</em>
+ * modules for different <em>account types</em>. Authenticators (which may be
+ * written by third parties) handle the actual details of validating account
+ * credentials and storing account information. For example, Google, Facebook,
+ * and Microsoft Exchange each have their own authenticator.
+ *
+ * <p>Many servers support some notion of an <em>authentication token</em>,
+ * which can be used to authenticate a request to the server without sending
+ * the user's actual password. (Auth tokens are normally created with a
+ * separate request which does include the user's credentials.) AccountManager
+ * can generate auth tokens for applications, so the application doesn't need to
+ * handle passwords directly. Auth tokens are normally reusable and cached by
+ * AccountManager, but must be refreshed periodically. It's the responsibility
+ * of applications to <em>invalidate</em> auth tokens when they stop working so
+ * the AccountManager knows it needs to regenerate them.
+ *
+ * <p>Applications accessing a server normally go through these steps:
+ *
+ * <ul>
+ * <li>Get an instance of AccountManager using {@link #get(Context)}.
+ *
+ * <li>List the available accounts using {@link #getAccountsByType} or
+ * {@link #getAccountsByTypeAndFeatures}. Normally applications will only
+ * be interested in accounts with one particular <em>type</em>, which
+ * identifies the authenticator. Account <em>features</em> are used to
+ * identify particular account subtypes and capabilities. Both the account
+ * type and features are authenticator-specific strings, and must be known by
+ * the application in coordination with its preferred authenticators.
+ *
+ * <li>Select one or more of the available accounts, possibly by asking the
+ * user for their preference. If no suitable accounts are available,
+ * {@link #addAccount} may be called to prompt the user to create an
+ * account of the appropriate type.
+ *
+ * <li><b>Important:</b> If the application is using a previously remembered
+ * account selection, it must make sure the account is still in the list
+ * of accounts returned by {@link #getAccountsByType}. Requesting an auth token
+ * for an account no longer on the device results in an undefined failure.
+ *
+ * <li>Request an auth token for the selected account(s) using one of the
+ * {@link #getAuthToken} methods or related helpers. Refer to the description
+ * of each method for exact usage and error handling details.
+ *
+ * <li>Make the request using the auth token. The form of the auth token,
+ * the format of the request, and the protocol used are all specific to the
+ * service you are accessing. The application may use whatever network and
+ * protocol libraries are useful.
+ *
+ * <li><b>Important:</b> If the request fails with an authentication error,
+ * it could be that a cached auth token is stale and no longer honored by
+ * the server. The application must call {@link #invalidateAuthToken} to remove
+ * the token from the cache, otherwise requests will continue failing! After
+ * invalidating the auth token, immediately go back to the "Request an auth
+ * token" step above. If the process fails the second time, then it can be
+ * treated as a "genuine" authentication failure and the user notified or other
+ * appropriate actions taken.
+ * </ul>
+ *
+ * <p>Some AccountManager methods may need to interact with the user to
+ * prompt for credentials, present options, or ask the user to add an account.
+ * The caller may choose whether to allow AccountManager to directly launch the
+ * necessary user interface and wait for the user, or to return an Intent which
+ * the caller may use to launch the interface, or (in some cases) to install a
+ * notification which the user can select at any time to launch the interface.
+ * To have AccountManager launch the interface directly, the caller must supply
+ * the current foreground {@link Activity} context.
+ *
+ * <p>Many AccountManager methods take {@link AccountManagerCallback} and
+ * {@link Handler} as parameters. These methods return immediately and
+ * run asynchronously. If a callback is provided then
+ * {@link AccountManagerCallback#run} will be invoked on the Handler's
+ * thread when the request completes, successfully or not.
+ * The result is retrieved by calling {@link AccountManagerFuture#getResult()}
+ * on the {@link AccountManagerFuture} returned by the method (and also passed
+ * to the callback). This method waits for the operation to complete (if
+ * necessary) and either returns the result or throws an exception if an error
+ * occurred during the operation. To make the request synchronously, call
+ * {@link AccountManagerFuture#getResult()} immediately on receiving the
+ * future from the method; no callback need be supplied.
+ *
+ * <p>Requests which may block, including
+ * {@link AccountManagerFuture#getResult()}, must never be called on
+ * the application's main event thread. These operations throw
+ * {@link IllegalStateException} if they are used on the main thread.
+ */
+@SystemService(Context.ACCOUNT_SERVICE)
+public class AccountManager {
+
+ private static final String TAG = "AccountManager";
+
+ public static final int ERROR_CODE_REMOTE_EXCEPTION = 1;
+ public static final int ERROR_CODE_NETWORK_ERROR = 3;
+ public static final int ERROR_CODE_CANCELED = 4;
+ public static final int ERROR_CODE_INVALID_RESPONSE = 5;
+ public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
+ public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
+ public static final int ERROR_CODE_BAD_REQUEST = 8;
+ public static final int ERROR_CODE_BAD_AUTHENTICATION = 9;
+
+ /** @hide */
+ public static final int ERROR_CODE_USER_RESTRICTED = 100;
+ /** @hide */
+ public static final int ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE = 101;
+
+ /**
+ * Bundle key used for the {@link String} account name in results
+ * from methods which return information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_NAME = "authAccount";
+
+ /**
+ * Bundle key used for the {@link String} account type in results
+ * from methods which return information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_TYPE = "accountType";
+
+ /**
+ * Bundle key used for the account access id used for noting the
+ * account was accessed when unmarshaled from a parcel.
+ *
+ * @hide
+ */
+ public static final String KEY_ACCOUNT_ACCESS_ID = "accountAccessId";
+
+ /**
+ * Bundle key used for the auth token value in results
+ * from {@link #getAuthToken} and friends.
+ */
+ public static final String KEY_AUTHTOKEN = "authtoken";
+
+ /**
+ * Bundle key used for an {@link Intent} in results from methods that
+ * may require the caller to interact with the user. The Intent can
+ * be used to start the corresponding user interface activity.
+ */
+ public static final String KEY_INTENT = "intent";
+
+ /**
+ * Bundle key used to supply the password directly in options to
+ * {@link #confirmCredentials}, rather than prompting the user with
+ * the standard password prompt.
+ */
+ public static final String KEY_PASSWORD = "password";
+
+ public static final String KEY_ACCOUNTS = "accounts";
+
+ public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
+ public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
+ public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
+ public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
+ public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
+ public static final String KEY_BOOLEAN_RESULT = "booleanResult";
+ public static final String KEY_ERROR_CODE = "errorCode";
+ public static final String KEY_ERROR_MESSAGE = "errorMessage";
+ public static final String KEY_USERDATA = "userdata";
+
+ /**
+ * Bundle key used to supply the last time the credentials of the account
+ * were authenticated successfully. Time is specified in milliseconds since
+ * epoch. Associated time is updated on successful authentication of account
+ * on adding account, confirming credentials, or updating credentials.
+ */
+ public static final String KEY_LAST_AUTHENTICATED_TIME = "lastAuthenticatedTime";
+
+ /**
+ * The UID of caller app.
+ */
+ public static final String KEY_CALLER_UID = "callerUid";
+
+ /**
+ * The process id of caller app.
+ */
+ public static final String KEY_CALLER_PID = "callerPid";
+
+ /**
+ * The Android package of the caller will be set in the options bundle by the
+ * {@link AccountManager} and will be passed to the AccountManagerService and
+ * to the AccountAuthenticators. The uid of the caller will be known by the
+ * AccountManagerService as well as the AccountAuthenticators so they will be able to
+ * verify that the package is consistent with the uid (a uid might be shared by many
+ * packages).
+ */
+ public static final String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
+
+ /**
+ * Boolean, if set and 'customTokens' the authenticator is responsible for
+ * notifications.
+ * @hide
+ */
+ public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
+
+ /**
+ * Bundle key used for a {@link Bundle} in result from
+ * {@link #startAddAccountSession} and friends which returns session data
+ * for installing an account later.
+ */
+ public static final String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+
+ /**
+ * Bundle key used for the {@link String} account status token in result
+ * from {@link #startAddAccountSession} and friends which returns
+ * information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
+
+ public static final String ACTION_AUTHENTICATOR_INTENT =
+ "android.accounts.AccountAuthenticator";
+ public static final String AUTHENTICATOR_META_DATA_NAME =
+ "android.accounts.AccountAuthenticator";
+ public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "VISIBILITY_" }, value = {
+ VISIBILITY_UNDEFINED,
+ VISIBILITY_VISIBLE,
+ VISIBILITY_USER_MANAGED_VISIBLE,
+ VISIBILITY_NOT_VISIBLE,
+ VISIBILITY_USER_MANAGED_NOT_VISIBLE
+ })
+ public @interface AccountVisibility {
+ }
+
+ /**
+ * Account visibility was not set. Default visibility value will be used.
+ * See {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE}, {@link #PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE}
+ */
+ public static final int VISIBILITY_UNDEFINED = 0;
+
+ /**
+ * Account is always visible to given application and only authenticator can revoke visibility.
+ */
+ public static final int VISIBILITY_VISIBLE = 1;
+
+ /**
+ * Account is visible to given application, but user can revoke visibility.
+ */
+ public static final int VISIBILITY_USER_MANAGED_VISIBLE = 2;
+
+ /**
+ * Account is not visible to given application and only authenticator can grant visibility.
+ */
+ public static final int VISIBILITY_NOT_VISIBLE = 3;
+
+ /**
+ * Account is not visible to given application, but user can reveal it, for example, using
+ * {@link #newChooseAccountIntent(Account, List, String[], String, String, String[], Bundle)}
+ */
+ public static final int VISIBILITY_USER_MANAGED_NOT_VISIBLE = 4;
+
+ /**
+ * Token type for the special case where a UID has access only to an account
+ * but no authenticator specific auth token types.
+ *
+ * @hide
+ */
+ public static final String ACCOUNT_ACCESS_TOKEN_TYPE =
+ "com.android.AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE";
+
+ /**
+ * @hide
+ */
+ public static final String CACHE_KEY_ACCOUNTS_DATA_PROPERTY = "cache_key.system_server.accounts_data";
+
+ /**
+ * @hide
+ */
+ public static final int CACHE_ACCOUNTS_DATA_SIZE = 4;
+
+ PropertyInvalidatedCache<UserPackage, Account[]> mAccountsForUserCache =
+ new PropertyInvalidatedCache<UserPackage, Account[]>(
+ CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) {
+ @Override
+ public Account[] recompute(UserPackage userAndPackage) {
+ try {
+ return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(UserPackage query) {
+ return query.userId < 0;
+ }
+ @Override
+ public boolean resultEquals(Account[] l, Account[] r) {
+ if (l == r) {
+ return true;
+ } else if (l == null || r == null) {
+ return false;
+ } else {
+ return Arrays.equals(l, r);
+ }
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static final String CACHE_KEY_USER_DATA_PROPERTY = "cache_key.system_server.account_user_data";
+
+ /**
+ * @hide
+ */
+ public static final int CACHE_USER_DATA_SIZE = 4;
+
+ private static final class AccountKeyData {
+ final public Account account;
+ final public String key;
+
+ public AccountKeyData(Account Account, String Key) {
+ this.account = Account;
+ this.key = Key;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (o == this) {
+ return true;
+ }
+
+ if (o.getClass() != getClass()) {
+ return false;
+ }
+
+ AccountKeyData e = (AccountKeyData) o;
+
+ return e.account.equals(account) && e.key.equals(key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(account,key);
+ }
+ }
+
+ PropertyInvalidatedCache<AccountKeyData, String> mUserDataCache =
+ new PropertyInvalidatedCache<AccountKeyData, String>(CACHE_USER_DATA_SIZE,
+ CACHE_KEY_USER_DATA_PROPERTY) {
+ @Override
+ public String recompute(AccountKeyData accountKeyData) {
+ Account account = accountKeyData.account;
+ String key = accountKeyData.key;
+
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
+ try {
+ return mService.getUserData(account, key);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ };
+
+ @UnsupportedAppUsage
+ private final Context mContext;
+ private final IAccountManager mService;
+ private final Handler mMainHandler;
+
+ /**
+ * Action sent as a broadcast Intent by the AccountsService when accounts are added, accounts
+ * are removed, or an account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
+ * @see #ACTION_ACCOUNT_REMOVED
+ *
+ * @deprecated use {@link #addOnAccountsUpdatedListener} to get account updates in runtime.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(includeBackground = true)
+ public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
+ "android.accounts.LOGIN_ACCOUNTS_CHANGED";
+
+ /**
+ * Action sent as a broadcast Intent by the AccountsService when any account is removed
+ * or renamed. Only applications which were able to see the account will receive the intent.
+ * Intent extra will include the following fields:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the removed account
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(includeBackground = true)
+ public static final String ACTION_ACCOUNT_REMOVED =
+ "android.accounts.action.ACCOUNT_REMOVED";
+
+ /**
+ * Action sent as a broadcast Intent to specific package by the AccountsService
+ * when account visibility or account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
+ *
+ * @hide
+ */
+ public static final String ACTION_VISIBLE_ACCOUNTS_CHANGED =
+ "android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
+
+ /**
+ * Key to set visibility for applications which satisfy one of the following conditions:
+ * <ul>
+ * <li>Target API level below {@link android.os.Build.VERSION_CODES#O} and have
+ * deprecated {@link android.Manifest.permission#GET_ACCOUNTS} permission.
+ * </li>
+ * <li> Have {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permission. </li>
+ * <li> Have the same signature as authenticator. </li>
+ * <li> Have {@link android.Manifest.permission#READ_CONTACTS} permission and
+ * account type may be associated with contacts data - (verified by
+ * {@link android.Manifest.permission#WRITE_CONTACTS} permission check for the authenticator).
+ * </li>
+ * </ul>
+ * See {@link #getAccountVisibility}. If the value was not set by authenticator
+ * {@link #VISIBILITY_USER_MANAGED_VISIBLE} is used.
+ */
+ public static final String PACKAGE_NAME_KEY_LEGACY_VISIBLE =
+ "android:accounts:key_legacy_visible";
+
+ /**
+ * Key to set default visibility for applications which don't satisfy conditions in
+ * {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE}. If the value was not set by authenticator
+ * {@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE} is used.
+ */
+ public static final String PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE =
+ "android:accounts:key_legacy_not_visible";
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AccountManager(Context context, IAccountManager service) {
+ mContext = context;
+ mService = service;
+ mMainHandler = new Handler(mContext.getMainLooper());
+ }
+
+ /**
+ * @hide used for testing only
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public AccountManager(Context context, IAccountManager service, Handler handler) {
+ mContext = context;
+ mService = service;
+ mMainHandler = handler;
+ }
+
+ /**
+ * @hide for internal use only
+ */
+ public static Bundle sanitizeResult(Bundle result) {
+ if (result != null) {
+ if (result.containsKey(KEY_AUTHTOKEN)
+ && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) {
+ final Bundle newResult = new Bundle(result);
+ newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>");
+ return newResult;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets an AccountManager instance associated with a Context.
+ * The {@link Context} will be used as long as the AccountManager is
+ * active, so make sure to use a {@link Context} whose lifetime is
+ * commensurate with any listeners registered to
+ * {@link #addOnAccountsUpdatedListener} or similar methods.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param context The {@link Context} to use when necessary
+ * @return An {@link AccountManager} instance
+ */
+ public static AccountManager get(Context context) {
+ if (context == null) throw new IllegalArgumentException("context is null");
+ return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
+ }
+
+ /**
+ * Gets the saved password associated with the account. This is intended for authenticators and
+ * related code; applications should get an auth token instead.
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before, AUTHENTICATE_ACCOUNTS
+ * permission is needed for those platforms. See docs for this function in API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ * @param account The account to query for a password. Must not be {@code null}.
+ * @return The account's password, null if none or if the account doesn't exist
+ */
+ public String getPassword(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.getPassword(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the user data named by "key" associated with the account. This is intended for
+ * authenticators and related code to store arbitrary metadata along with accounts. The meaning
+ * of the keys and values is up to the authenticator for the account.
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before, AUTHENTICATE_ACCOUNTS
+ * permission is needed for those platforms. See docs for this function in API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ * @param account The account to query for user data
+ * @return The user data, null if the account, key doesn't exist, or the user is locked
+ */
+ public String getUserData(final Account account, final String key) {
+ return mUserDataCache.query(new AccountKeyData(account,key));
+ }
+
+ /**
+ * Lists the currently registered authenticators.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * <p>Caller targeting API level 34 and above, the results are filtered
+ * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
+ *
+ * @return An array of {@link AuthenticatorDescription} for every
+ * authenticator known to the AccountManager service. Empty (never
+ * null) if no authenticators are known.
+ */
+ @UserHandleAware
+ public AuthenticatorDescription[] getAuthenticatorTypes() {
+ return getAuthenticatorTypesAsUser(mContext.getUserId());
+ }
+
+ /**
+ * @hide
+ * Lists the currently registered authenticators for a given user id.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>The caller has to be in the same user or have the permission
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @return An array of {@link AuthenticatorDescription} for every
+ * authenticator known to the AccountManager service. Empty (never
+ * null) if no authenticators are known.
+ */
+ public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
+ try {
+ return mService.getAuthenticatorTypes(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Lists all accounts visible to the caller regardless of type. Equivalent to
+ * getAccountsByType(null). These accounts may be visible because the user granted access to the
+ * account, or the AbstractAccountAuthenticator managing the account did so or because the
+ * client shares a signature with the managing AbstractAccountAuthenticator.
+ *
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
+ * have been added.
+ */
+ @UserHandleAware
+ @NonNull
+ public Account[] getAccounts() {
+ return getAccountsAsUser(mContext.getUserId());
+ }
+
+ /**
+ * @hide
+ * Lists all accounts visible to caller regardless of type for a given user id. Equivalent to
+ * getAccountsByType(null).
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
+ * have been added.
+ */
+ @NonNull
+ public Account[] getAccountsAsUser(int userId) {
+ UserPackage userAndPackage = UserPackage.of(userId, mContext.getOpPackageName());
+ return mAccountsForUserCache.query(userAndPackage);
+ }
+
+ /**
+ * @hide
+ * For use by internal activities. Returns the list of accounts that the calling package
+ * is authorized to use, particularly for shared accounts.
+ * @param packageName package name of the calling app.
+ * @param uid the uid of the calling app.
+ * @return the accounts that are available to this package and user.
+ */
+ @NonNull
+ public Account[] getAccountsForPackage(String packageName, int uid) {
+ try {
+ return mService.getAccountsForPackage(packageName, uid, mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the accounts visible to the specified package in an environment where some apps are
+ * not authorized to view all accounts. This method can only be called by system apps and
+ * authenticators managing the type.
+ * Beginning API level {@link android.os.Build.VERSION_CODES#O} it also return accounts
+ * which user can make visible to the application (see {@link #VISIBILITY_USER_MANAGED_VISIBLE}).
+ *
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @param packageName The package name of the app for which the accounts are to be returned
+ * @return An array of {@link Account}, one per matching account. Empty (never null) if no
+ * accounts of the specified type can be accessed by the package.
+ *
+ */
+ @NonNull
+ public Account[] getAccountsByTypeForPackage(String type, String packageName) {
+ try {
+ return mService.getAccountsByTypeForPackage(type, packageName,
+ mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Lists all accounts of particular type visible to the caller. These accounts may be visible
+ * because the user granted access to the account, or the AbstractAccountAuthenticator managing
+ * the account did so or because the client shares a signature with the managing
+ * AbstractAccountAuthenticator.
+ *
+ * <p>
+ * The account type is a string token corresponding to the authenticator and useful domain of
+ * the account. For example, there are types corresponding to Google and Facebook. The exact
+ * string token to use will be published somewhere associated with the authenticator in
+ * question.
+ * </p>
+ *
+ * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+ * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+ * disclose that fact to users. For apps published on Google Play, policies protecting user data
+ * require that you do the following:</p>
+ * <ul>
+ * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+ * sensitive data. Learn more about
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+ * disclosure and consent</a>.</li>
+ * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+ * </ul>
+ * <p>To learn more, visit the
+ * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+ * Policy regarding user data</a>.</p></div>
+ *
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * <p>
+ * Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
+ * of accounts made visible to it by user
+ * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}) or AbstractAccountAuthenticator
+ * using {@link #setAccountVisibility}.
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is not used.
+ *
+ * <p>
+ * Caller targeting API level below {@link android.os.Build.VERSION_CODES#O} that have not been
+ * granted the {@link android.Manifest.permission#GET_ACCOUNTS} permission, will only see those
+ * accounts managed by AbstractAccountAuthenticators whose signature matches the client.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before,
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is
+ * needed for those platforms, irrespective of uid or signature match. See docs for this
+ * function in API level {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @return An array of {@link Account}, one per matching account. Empty (never null) if no
+ * accounts of the specified type have been added.
+ */
+ @UserHandleAware
+ @NonNull
+ public Account[] getAccountsByType(String type) {
+ return getAccountsByTypeAsUser(type, mContext.getUser());
+ }
+
+ /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
+ @NonNull
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {
+ try {
+ return mService.getAccountsAsUser(type, userHandle.getIdentifier(),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Change whether or not an app (identified by its uid) is allowed to retrieve an authToken
+ * for an account.
+ * <p>
+ * This is only meant to be used by system activities and is not in the SDK.
+ * @param account The account whose permissions are being modified
+ * @param authTokenType The type of token whose permissions are being modified
+ * @param uid The uid that identifies the app which is being granted or revoked permission.
+ * @param value true is permission is being granted, false for revoked
+ * @hide
+ */
+ public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) {
+ try {
+ mService.updateAppPermission(account, authTokenType, uid, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the user-friendly label associated with an authenticator's auth token.
+ * @param accountType the type of the authenticator. must not be null.
+ * @param authTokenType the token type. must not be null.
+ * @param callback callback to invoke when the result is available. may be null.
+ * @param handler the handler on which to invoke the callback, or null for the main thread
+ * @return a future containing the label string
+ * @hide
+ */
+ public AccountManagerFuture<String> getAuthTokenLabel(
+ final String accountType, final String authTokenType,
+ AccountManagerCallback<String> callback, Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ return new Future2Task<String>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAuthTokenLabel(mResponse, accountType, authTokenType);
+ }
+
+ @Override
+ public String bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_AUTH_TOKEN_LABEL)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getString(KEY_AUTH_TOKEN_LABEL);
+ }
+ }.start();
+ }
+
+ /**
+ * Finds out whether a particular account has all the specified features. Account features are
+ * authenticator-specific string tokens identifying boolean account properties. For example,
+ * features are used to tell whether Google accounts have a particular service (such as Google
+ * Calendar or Google Talk) enabled. The feature names and their meanings are published
+ * somewhere associated with the authenticator in question.
+ *
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * <p>
+ * If caller target API level is below {@link android.os.Build.VERSION_CODES#O}, it is
+ * required to hold the permission {@link android.Manifest.permission#GET_ACCOUNTS} or have a
+ * signature match with the AbstractAccountAuthenticator that manages the account.
+ *
+ * @param account The {@link Account} to test
+ * @param features An array of the account features to check
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the account
+ * exists and has all of the specified features.
+ */
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS_FULL)
+ public AccountManagerFuture<Boolean> hasFeatures(final Account account,
+ final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return hasFeaturesAsUser(account, features, callback, handler, mContext.getUserId());
+ }
+
+ private AccountManagerFuture<Boolean> hasFeaturesAsUser(
+ final Account account, final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler, int userId) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (features == null) throw new IllegalArgumentException("features is null");
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.hasFeatures(
+ mResponse, account, features, userId, mContext.getOpPackageName());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Lists all accounts of a type which have certain features. The account type identifies the
+ * authenticator (see {@link #getAccountsByType}). Account features are authenticator-specific
+ * string tokens identifying boolean account properties (see {@link #hasFeatures}).
+ *
+ * <p>
+ * Unlike {@link #getAccountsByType}, this method calls the authenticator, which may contact the
+ * server or do other work to check account features, so the method returns an
+ * {@link AccountManagerFuture}.
+ *
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * <p>
+ * Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
+ * of accounts made visible to it by user
+ * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}) or AbstractAccountAuthenticator
+ * using {@link #setAccountVisibility}.
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is not used.
+ *
+ * <p>
+ * Caller targeting API level below {@link android.os.Build.VERSION_CODES#O} that have not been
+ * granted the {@link android.Manifest.permission#GET_ACCOUNTS} permission, will only see those
+ * accounts managed by AbstractAccountAuthenticators whose signature matches the client.
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and before,
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission is
+ * needed for those platforms, irrespective of uid or signature match. See docs for this
+ * function in API level {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ *
+ *
+ * @param type The type of accounts to return, must not be null
+ * @param features An array of the account features to require, may be null or empty *
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to an array of {@link Account}, one
+ * per account of the specified type which matches the requested features.
+ */
+ public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
+ final String type, final String[] features,
+ AccountManagerCallback<Account[]> callback, Handler handler) {
+ if (type == null) throw new IllegalArgumentException("type is null");
+ return new Future2Task<Account[]>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAccountsByFeatures(mResponse, type, features,
+ mContext.getOpPackageName());
+ }
+ @Override
+ public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_ACCOUNTS)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
+ Account[] descs = new Account[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ descs[i] = (Account) parcelables[i];
+ }
+ return descs;
+ }
+ }.start();
+ }
+
+ /**
+ * Adds an account directly to the AccountManager. Normally used by sign-up
+ * wizards associated with authenticators, not directly by applications.
+ * <p>Calling this method does not update the last authenticated timestamp,
+ * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+ * {@link #notifyAccountAuthenticated(Account)} after getting success.
+ * However, if this method is called when it is triggered by addAccount() or
+ * addAccountAsUser() or similar functions, then there is no need to update
+ * timestamp manually as it is updated automatically by framework on
+ * successful completion of the mentioned functions.
+ * <p>It is safe to call this method from the main thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that owns the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission is needed for those platforms. See docs
+ * for this function in API level 22.
+ *
+ * @param account The {@link Account} to add
+ * @param password The password to associate with the account, null for none
+ * @param userdata String values to use for the account's userdata, null for
+ * none
+ * @return True if the account was successfully added, false if the account
+ * already exists, the account is null, the user is locked, or another error occurs.
+ */
+ public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.addAccountExplicitly(
+ account, password, userdata, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds an account directly to the AccountManager. Additionally it specifies Account visibility
+ * for given list of packages.
+ * <p>
+ * Normally used by sign-up wizards associated with authenticators, not directly by
+ * applications.
+ * <p>
+ * Calling this method does not update the last authenticated timestamp, referred by
+ * {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+ * {@link #notifyAccountAuthenticated(Account)} after getting success.
+ * <p>
+ * It is safe to call this method from the main thread.
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account The {@link Account} to add
+ * @param password The password to associate with the account, null for none
+ * @param extras String values to use for the account's userdata, null for none
+ * @param visibility Map from packageName to visibility values which will be set before account
+ * is added. See {@link #getAccountVisibility} for possible values. Declaring
+ * <a href="/training/basics/intents/package-visibility">package visibility</a> needs for
+ * package names in the map is needed, if the caller is targeting API level 34 and above.
+ *
+ * @return True if the account was successfully added, false if the account already exists, the
+ * account is null, or another error occurs.
+ */
+ public boolean addAccountExplicitly(Account account, String password, Bundle extras,
+ Map<String, Integer> visibility) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.addAccountExplicitlyWithVisibility(account, password, extras,
+ visibility, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns package names and visibility which were explicitly set for given account.
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account The account for which visibility data should be returned
+ *
+ * @return Map from package names to visibility for given account
+ */
+ public Map<String, Integer> getPackagesAndVisibilityForAccount(Account account) {
+ try {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result = (Map<String, Integer>) mService
+ .getPackagesAndVisibilityForAccount(account);
+ return result;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets all accounts of given type and their visibility for specific package. This method
+ * requires the caller to have a signature match with the authenticator that manages
+ * accountType. It is a helper method which combines calls to {@link #getAccountsByType} by
+ * authenticator and {@link #getAccountVisibility} for every returned account.
+ *
+ * <p>
+ *
+ * @param packageName Package name
+ * @param accountType {@link Account} type
+ *
+ * @return Map with visibility for all accounts of given type
+ * See {@link #getAccountVisibility} for possible values
+ */
+ public Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
+ String accountType) {
+ try {
+ @SuppressWarnings("unchecked")
+ Map<Account, Integer> result = (Map<Account, Integer>) mService
+ .getAccountsAndVisibilityForPackage(packageName, accountType);
+ return result;
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set visibility value of given account to certain package.
+ * Package name must match installed application, or be equal to
+ * {@link #PACKAGE_NAME_KEY_LEGACY_VISIBLE} or {@link #PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE}.
+ * <p>
+ * Possible visibility values:
+ * <ul>
+ * <li>{@link #VISIBILITY_UNDEFINED}</li>
+ * <li>{@link #VISIBILITY_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_NOT_VISIBLE}
+ * <li>{@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE}</li>
+ * </ul>
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account {@link Account} to update visibility
+ * @param packageName Package name of the application to modify account visibility. Declaring
+ * <a href="/training/basics/intents/package-visibility">package visibility</a> needs
+ * for it is needed, if the caller is targeting API level 34 and above.
+ * @param visibility New visibility value
+ *
+ * @return True, if visibility value was successfully updated.
+ */
+ public boolean setAccountVisibility(Account account, String packageName,
+ @AccountVisibility int visibility) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.setAccountVisibility(account, packageName, visibility);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get visibility of certain account for given application. Possible returned values are:
+ * <ul>
+ * <li>{@link #VISIBILITY_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_USER_MANAGED_VISIBLE}</li>
+ * <li>{@link #VISIBILITY_NOT_VISIBLE}
+ * <li>{@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE}</li>
+ * </ul>
+ *
+ * <p>
+ * This method requires the caller to have a signature match with the authenticator that owns
+ * the specified account.
+ *
+ * @param account {@link Account} to get visibility
+ * @param packageName Package name of the application to get account visibility
+ *
+ * @return int Visibility of given account. For the caller targeting API level 34 and above,
+ * {@link #VISIBILITY_NOT_VISIBLE} is returned if the given package is filtered by the rules of
+ * <a href="/training/basics/intents/package-visibility">package visibility</a>.
+ */
+ public @AccountVisibility int getAccountVisibility(Account account, String packageName) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.getAccountVisibility(account, packageName);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies the system that the account has just been authenticated. This
+ * information may be used by other applications to verify the account. This
+ * should be called only when the user has entered correct credentials for
+ * the account.
+ * <p>
+ * It is not safe to call this method from the main thread. As such, call it
+ * from another thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that owns the specified account.
+ *
+ * @param account The {@link Account} to be updated.
+ * @return boolean {@code true} if the authentication of the account has been successfully
+ * acknowledged. Otherwise {@code false}.
+ */
+ public boolean notifyAccountAuthenticated(Account account) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.accountAuthenticated(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Rename the specified {@link Account}. This is equivalent to removing
+ * the existing account and adding a new renamed account with the old
+ * account's user data.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The {@link Account} to rename
+ * @param newName String name to be associated with the account.
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to the Account
+ * after the name change. If successful the account's name will be the
+ * specified new name.
+ */
+ public AccountManagerFuture<Account> renameAccount(
+ final Account account,
+ @Size(min = 1) final String newName,
+ AccountManagerCallback<Account> callback,
+ Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null.");
+ if (TextUtils.isEmpty(newName)) {
+ throw new IllegalArgumentException("newName is empty or null.");
+ }
+ return new Future2Task<Account>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.renameAccount(mResponse, account, newName);
+ }
+ @Override
+ public Account bundleToResult(Bundle bundle) throws AuthenticatorException {
+ String name = bundle.getString(KEY_ACCOUNT_NAME);
+ String type = bundle.getString(KEY_ACCOUNT_TYPE);
+ String accessId = bundle.getString(KEY_ACCOUNT_ACCESS_ID);
+ return new Account(name, type, accessId);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets the previous name associated with the account or {@code null}, if
+ * none. This is intended so that clients of
+ * {@link OnAccountsUpdateListener} can determine if an
+ * authenticator has renamed an account.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * @param account The account to query for a previous name.
+ * @return The account's previous name, null if the account has never been
+ * renamed.
+ */
+ public String getPreviousName(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.getPreviousName(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The {@link Account} to remove
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean,
+ * true if the account has been successfully removed
+ * @deprecated use
+ * {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
+ * instead
+ */
+ @UserHandleAware
+ @Deprecated
+ public AccountManagerFuture<Boolean> removeAccount(final Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ return removeAccountAsUser(account, callback, handler, mContext.getUser());
+ }
+
+ /**
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The {@link Account} to remove
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to delete an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the {@link Intent} may be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account
+ * was removed or if active. If no activity was specified, the returned
+ * Bundle contains only {@link #KEY_INTENT} with the {@link Intent}
+ * needed to launch the actual account removal process, if authenticator
+ * needs the activity launch. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process or
+ * adding accounts (of this type) has been disabled by policy
+ * </ul>
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> removeAccount(final Account account,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
+ }
+
+ /**
+ * @see #removeAccount(Account, AccountManagerCallback, Handler)
+ * @hide
+ * @deprecated use
+ * {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)}
+ * instead
+ */
+ @Deprecated
+ public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account,
+ AccountManagerCallback<Boolean> callback, Handler handler,
+ final UserHandle userHandle) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Bundle> removeAccountAsUser(final Account account,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler,
+ final UserHandle userHandle) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ if (userHandle == null)
+ throw new IllegalArgumentException("userHandle is null");
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.removeAccountAsUser(mResponse, account, activity != null,
+ userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+ /**
+ * Removes an account directly. Normally used by authenticators, not
+ * directly by applications. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account deletion,
+ * in which case the account will not be deleted.
+ * <p>
+ * It is safe to call this method from the main thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The {@link Account} to delete.
+ * @return True if the account was successfully deleted, false if the
+ * account did not exist, the account is null, or another error
+ * occurs.
+ */
+ public boolean removeAccountExplicitly(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ return mService.removeAccountExplicitly(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes an auth token from the AccountManager's cache. Does nothing if
+ * the auth token is not currently in the cache. Applications must call this
+ * method when the auth token is found to have expired or otherwise become
+ * invalid for authenticating requests. The AccountManager does not validate
+ * or expire cached auth tokens otherwise.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS or USE_CREDENTIALS permission is needed for those
+ * platforms. See docs for this function in API level 22.
+ *
+ * @param accountType The account type of the auth token to invalidate, must not be null
+ * @param authToken The auth token to invalidate, may be null
+ */
+ public void invalidateAuthToken(final String accountType, final String authToken) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ try {
+ if (authToken != null) {
+ mService.invalidateAuthToken(accountType, authToken);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets an auth token from the AccountManager's cache. If no auth
+ * token is cached for this account, null will be returned -- a new
+ * auth token will not be generated, and the server will not be contacted.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The account for which an auth token is to be fetched. Cannot be {@code null}.
+ * @param authTokenType The type of auth token to fetch. Cannot be {@code null}.
+ * @return The cached auth token for this account and type, or null if
+ * no auth token is cached, the account does not exist, or the user is locked
+ * @see #getAuthToken
+ */
+ public String peekAuthToken(final Account account, final String authTokenType) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ try {
+ return mService.peekAuthToken(account, authTokenType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets or forgets a saved password. This modifies the local copy of the
+ * password used to automatically authenticate the user; it does not change
+ * the user's account password on the server. Intended for use by the
+ * authenticator, not directly by applications.
+ * <p>Calling this method does not update the last authenticated timestamp,
+ * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+ * {@link #notifyAccountAuthenticated(Account)} after getting success.
+ * <p>It is safe to call this method from the main thread.
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The account whose password is to be set. Cannot be
+ * {@code null}.
+ * @param password The password to set, null to clear the password
+ */
+ public void setPassword(final Account account, final String password) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ mService.setPassword(account, password);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Forgets a saved password. This erases the local copy of the password;
+ * it does not change the user's account password on the server.
+ * Has the same effect as setPassword(account, null) but requires fewer
+ * permissions, and may be used by applications or management interfaces
+ * to "sign out" from an account.
+ *
+ * <p>This method only successfully clear the account's password when the
+ * caller has the same signature as the authenticator that owns the
+ * specified account. Otherwise, this method will silently fail.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account whose password to clear
+ */
+ public void clearPassword(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ try {
+ mService.clearPassword(account);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets one userdata key for an account. Intended by use for the
+ * authenticator to stash state for itself, not directly by applications.
+ * The meaning of the keys and values is up to the authenticator.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account Account whose user data is to be set. Must not be {@code null}.
+ * @param key String user data key to set. Must not be null
+ * @param value String value to set, {@code null} to clear this user data key
+ */
+ public void setUserData(final Account account, final String key, final String value) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
+ try {
+ mService.setUserData(account, key, value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds an auth token to the AccountManager cache for an account.
+ * If the account does not exist then this call has no effect.
+ * Replaces any previous auth token for this account and auth token type.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to have a signature match with the
+ * authenticator that manages the specified account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+ * is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param account The account to set an auth token for
+ * @param authTokenType The type of the auth token, see {#getAuthToken}
+ * @param authToken The auth token to add to the cache
+ */
+ public void setAuthToken(Account account, final String authTokenType, final String authToken) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ try {
+ mService.setAuthToken(account, authTokenType, authToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This convenience helper synchronously gets an auth token with
+ * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
+ *
+ * <p>This method may block while a network request completes, and must
+ * never be made from the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * USE_CREDENTIALS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, see {@link #getAuthToken getAuthToken()}
+ * @param notifyAuthFailure If true, display a notification and return null
+ * if authentication fails; if false, prompt and wait for the user to
+ * re-enter correct credentials before returning
+ * @return An auth token of the specified type for this account, or null
+ * if authentication fails or none can be fetched.
+ * @throws AuthenticatorException if the authenticator failed to respond
+ * @throws OperationCanceledException if the request was canceled for any
+ * reason, including the user canceling a credential request
+ * @throws java.io.IOException if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ */
+ public String blockingGetAuthToken(Account account, String authTokenType,
+ boolean notifyAuthFailure)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
+ null /* handler */).getResult();
+ if (bundle == null) {
+ // This should never happen, but it does, occasionally. If it does return null to
+ // signify that we were not able to get the authtoken.
+ // TODO: remove this when the bug is found that sometimes causes a null bundle to be
+ // returned
+ Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for "
+ + account + ", authTokenType " + authTokenType);
+ return null;
+ }
+ return bundle.getString(KEY_AUTHTOKEN);
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * prompting the user for credentials if necessary. This method is
+ * intended for applications running in the foreground where it makes
+ * sense to ask the user directly for a password.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, the user is prompted to enter a password.
+ *
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * USE_CREDENTIALS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param options Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user for a password
+ * if necessary; used only to call startActivity(); must not be null.
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If an auth token
+ * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType, final Bundle options,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAuthToken(mResponse, account, authTokenType,
+ false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * optionally raising a notification if the user must enter credentials.
+ * This method is intended for background tasks and services where the
+ * user should not be immediately interrupted with a password prompt.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, an {@link Intent} is returned which, when started, will
+ * prompt the user for a password. If the notifyAuthFailure parameter is
+ * set, a status bar notification is also created with the same Intent,
+ * alerting the user that they need to enter a password at some point.
+ *
+ * <p>In that case, you may need to wait until the user responds, which
+ * could take hours or days or forever. When the user does respond and
+ * supply a new password, the account manager will broadcast the
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+ * notify {@link OnAccountsUpdateListener} which applications can
+ * use to try again.
+ *
+ * <p>If notifyAuthFailure is not set, it is the application's
+ * responsibility to launch the returned Intent at some point.
+ * Either way, the result from this call will not wait for user action.
+ *
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param notifyAuthFailure True to add a notification to prompt the
+ * user for a password if necessary, false to leave that to the caller
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields on success:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If the user
+ * must enter credentials, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+ *
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
+ * @deprecated use {@link #getAuthToken(Account, String, android.os.Bundle,
+ * boolean, AccountManagerCallback, android.os.Handler)} instead
+ */
+ @Deprecated
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType,
+ final boolean notifyAuthFailure,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ return getAuthToken(account, authTokenType, null, notifyAuthFailure, callback,
+ handler);
+ }
+
+ /**
+ * Gets an auth token of the specified type for a particular account,
+ * optionally raising a notification if the user must enter credentials.
+ * This method is intended for background tasks and services where the
+ * user should not be immediately interrupted with a password prompt.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, an {@link Intent} is returned which, when started, will
+ * prompt the user for a password. If the notifyAuthFailure parameter is
+ * set, a status bar notification is also created with the same Intent,
+ * alerting the user that they need to enter a password at some point.
+ *
+ * <p>In that case, you may need to wait until the user responds, which
+ * could take hours or days or forever. When the user does respond and
+ * supply a new password, the account manager will broadcast the
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+ * notify {@link OnAccountsUpdateListener} which applications can
+ * use to try again.
+ *
+ * <p>If notifyAuthFailure is not set, it is the application's
+ * responsibility to launch the returned Intent at some point.
+ * Either way, the result from this call will not wait for user action.
+ *
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * USE_CREDENTIALS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param options Authenticator-specific options for the request,
+ * may be null or empty
+ * @param notifyAuthFailure True to add a notification to prompt the
+ * user for a password if necessary, false to leave that to the caller
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields on success:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If the user
+ * must enter credentials, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+ *
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
+ * </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
+ */
+ public AccountManagerFuture<Bundle> getAuthToken(
+ final Account account, final String authTokenType, final Bundle options,
+ final boolean notifyAuthFailure,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+ return new AmsTask(null, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAuthToken(mResponse, account, authTokenType,
+ notifyAuthFailure, false /* expectActivityLaunch */, optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to add an account of a specified type. The authenticator
+ * for this account type processes this request with the appropriate user
+ * interface. If the user does elect to create a new account, the account
+ * name is returned.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param addAccountOptions Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to create an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and an account was created:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process or adding accounts
+ * (of this type) has been disabled by policy
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new account, usually because of network trouble
+ * </ul>
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> addAccount(final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle addAccountOptions,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (Process.myUserHandle().equals(mContext.getUser())) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.addAccount(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn);
+ }
+ }.start();
+ } else {
+ return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions,
+ activity, callback, handler, mContext.getUser());
+ }
+ }
+
+ /**
+ * @see #addAccount(String, String, String[], Bundle, Activity, AccountManagerCallback, Handler)
+ * @hide
+ */
+ public AccountManagerFuture<Bundle> addAccountAsUser(final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final Bundle addAccountOptions, final Activity activity,
+ AccountManagerCallback<Bundle> callback, Handler handler, final UserHandle userHandle) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.addAccountAsUser(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn, userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+
+ /**
+ * Adds shared accounts from a parent user to a secondary user. Adding the shared account
+ * doesn't take effect immediately. When the target user starts up, any pending shared accounts
+ * are attempted to be copied to the target user from the primary via calls to the
+ * authenticator.
+ * @param parentUser parent user
+ * @param user target user
+ * @hide
+ */
+ public void addSharedAccountsFromParentUser(UserHandle parentUser, UserHandle user) {
+ try {
+ mService.addSharedAccountsFromParentUser(parentUser.getIdentifier(),
+ user.getIdentifier(), mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Copies an account from one user to another user.
+ * @param account the account to copy
+ * @param fromUser the user to copy the account from
+ * @param toUser the target user
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
+ * succeeded.
+ * @hide
+ */
+ public AccountManagerFuture<Boolean> copyAccountToUser(
+ final Account account, final UserHandle fromUser, final UserHandle toUser,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (toUser == null || fromUser == null) {
+ throw new IllegalArgumentException("fromUser and toUser cannot be null");
+ }
+
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.copyAccountToUser(
+ mResponse, account, fromUser.getIdentifier(), toUser.getIdentifier());
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Confirms that the user knows the password for an account to make extra
+ * sure they are the owner of the account. The user-entered password can
+ * be supplied directly, otherwise the authenticator for this account type
+ * prompts the user with the appropriate interface. This method is
+ * intended for applications which want extra assurance; for example, the
+ * phone lock screen uses this to let the user unlock the phone with an
+ * account password if they forget the lock pattern.
+ *
+ * <p>If the user-entered password matches a saved password for this
+ * account, the request is considered valid; otherwise the authenticator
+ * verifies the password (usually by contacting the server).
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs
+ * for this function in API level 22.
+ *
+ * @param account The account to confirm password knowledge for
+ * @param options Authenticator-specific options for the request;
+ * if the {@link #KEY_PASSWORD} string field is present, the
+ * authenticator may use it directly rather than prompting the user;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if activity or password was supplied and
+ * the account was successfully verified:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
+ * </ul>
+ *
+ * If no activity or password was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>Also the returning Bundle may contain {@link
+ * #KEY_LAST_AUTHENTICATED_TIME} indicating the last time the
+ * credential was validated/created.
+ *
+ * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
+ * </ul>
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ return confirmCredentialsAsUser(account, options, activity, callback, handler,
+ mContext.getUser());
+ }
+
+ /**
+ * @hide
+ * Same as {@link #confirmCredentials(Account, Bundle, Activity, AccountManagerCallback, Handler)}
+ * but for the specified user.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public AccountManagerFuture<Bundle> confirmCredentialsAsUser(final Account account,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler, UserHandle userHandle) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ final int userId = userHandle.getIdentifier();
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.confirmCredentialsAsUser(mResponse, account, options, activity != null,
+ userId);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to enter a new password for an account, updating the
+ * saved credentials for the account. Normally this happens automatically
+ * when the server rejects credentials during an auth token fetch, but this
+ * can be invoked directly to ensure we have the correct credentials stored.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+ * this function in API level 22.
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token
+ * of this type to be created (but no actual auth token is returned);
+ * may be null
+ * @param options Authenticator-specific options for the request;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if an activity was supplied and the account
+ * credentials were successfully updated:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> updateCredentials(final Account account,
+ final String authTokenType,
+ final Bundle options, final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.updateCredentials(mResponse, account, authTokenType, activity != null,
+ options);
+ }
+ }.start();
+ }
+
+ /**
+ * Offers the user an opportunity to change an authenticator's settings.
+ * These properties are for the authenticator in general, not a particular
+ * account. Not all authenticators support this method.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to have the same signature as the
+ * authenticator associated with the specified account type.
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * MANAGE_ACCOUNTS permission is needed for those platforms. See docs
+ * for this function in API level 22.
+ *
+ * @param accountType The account type associated with the authenticator
+ * to adjust
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to adjust authenticator settings;
+ * used only to call startActivity(); if null, the settings dialog will
+ * not be launched directly, but the necessary {@link Intent} will be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * which is empty if properties were edited successfully, or
+ * if no activity was specified, contains only {@link #KEY_INTENT}
+ * needed to launch the authenticator's settings dialog.
+ * If an error occurred, {@link AccountManagerFuture#getResult()}
+ * throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the settings dialog
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * updating settings, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> editProperties(final String accountType,
+ final Activity activity, final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.editProperties(mResponse, accountType, activity != null);
+ }
+ }.start();
+ }
+
+ /**
+ * @hide
+ * Checks if the given account exists on any of the users on the device.
+ * Only the system process can call this method.
+ *
+ * @param account The account to check for existence.
+ * @return whether any user has this account
+ */
+ public boolean someUserHasAccount(@NonNull final Account account) {
+ try {
+ return mService.someUserHasAccount(account);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ private void ensureNotOnMainThread() {
+ final Looper looper = Looper.myLooper();
+ if (looper != null && looper == mContext.getMainLooper()) {
+ final IllegalStateException exception = new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
+ exception);
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ throw exception;
+ }
+ }
+ }
+
+ private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
+ final AccountManagerFuture<Bundle> future) {
+ handler = handler == null ? mMainHandler : handler;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.run(future);
+ }
+ });
+ }
+
+ private void postToHandler(Handler handler, final OnAccountsUpdateListener listener,
+ final Account[] accounts) {
+ final Account[] accountsCopy = new Account[accounts.length];
+ // send a copy to make sure that one doesn't
+ // change what another sees
+ System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
+ handler = (handler == null) ? mMainHandler : handler;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mAccountsUpdatedListeners) {
+ try {
+ if (mAccountsUpdatedListeners.containsKey(listener)) {
+ Set<String> types = mAccountsUpdatedListenersTypes.get(listener);
+ if (types != null) {
+ // filter by account type;
+ ArrayList<Account> filtered = new ArrayList<>();
+ for (Account account : accountsCopy) {
+ if (types.contains(account.type)) {
+ filtered.add(account);
+ }
+ }
+ listener.onAccountsUpdated(
+ filtered.toArray(new Account[filtered.size()]));
+ } else {
+ listener.onAccountsUpdated(accountsCopy);
+ }
+ }
+ } catch (SQLException e) {
+ // Better luck next time. If the problem was disk-full,
+ // the STORAGE_OK intent will re-trigger the update.
+ Log.e(TAG, "Can't update accounts", e);
+ }
+ }
+ }
+ });
+ }
+
+ private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final IAccountManagerResponse mResponse;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final Handler mHandler;
+ final AccountManagerCallback<Bundle> mCallback;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final Activity mActivity;
+ public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
+ super(new Callable<Bundle>() {
+ @Override
+ public Bundle call() throws Exception {
+ throw new IllegalStateException("this should never be called");
+ }
+ });
+
+ mHandler = handler;
+ mCallback = callback;
+ mActivity = activity;
+ mResponse = new Response();
+ }
+
+ public final AccountManagerFuture<Bundle> start() {
+ try {
+ doWork();
+ } catch (RemoteException e) {
+ setException(e);
+ }
+ return this;
+ }
+
+ @Override
+ protected void set(Bundle bundle) {
+ // TODO: somehow a null is being set as the result of the Future. Log this
+ // case to help debug where this is occurring. When this bug is fixed this
+ // condition statement should be removed.
+ if (bundle == null) {
+ Log.e(TAG, "the bundle must not be null", new Exception());
+ }
+ super.set(bundle);
+ }
+
+ public abstract void doWork() throws RemoteException;
+
+ private Bundle internalGetResult(Long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
+ try {
+ if (timeout == null) {
+ return get();
+ } else {
+ return get(timeout, unit);
+ }
+ } catch (CancellationException e) {
+ throw new OperationCanceledException();
+ } catch (TimeoutException e) {
+ // fall through and cancel
+ } catch (InterruptedException e) {
+ // fall through and cancel
+ } catch (ExecutionException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ } else if (cause instanceof UnsupportedOperationException) {
+ throw new AuthenticatorException(cause);
+ } else if (cause instanceof AuthenticatorException) {
+ throw (AuthenticatorException) cause;
+ } else if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ } else {
+ throw new IllegalStateException(cause);
+ }
+ } finally {
+ cancel(true /* interrupt if running */);
+ }
+ throw new OperationCanceledException();
+ }
+
+ @Override
+ public Bundle getResult()
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(null, null);
+ }
+
+ @Override
+ public Bundle getResult(long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(timeout, unit);
+ }
+
+ @Override
+ protected void done() {
+ if (mCallback != null) {
+ postToHandler(mHandler, mCallback, this);
+ }
+ }
+
+ /** Handles the responses from the AccountManager */
+ private class Response extends IAccountManagerResponse.Stub {
+ @Override
+ public void onResult(Bundle bundle) {
+ if (bundle == null) {
+ onError(ERROR_CODE_INVALID_RESPONSE, "null bundle returned");
+ return;
+ }
+ Intent intent = bundle.getParcelable(KEY_INTENT, android.content.Intent.class);
+ if (intent != null && mActivity != null) {
+ // since the user provided an Activity we will silently start intents
+ // that we see
+ mActivity.startActivity(intent);
+ // leave the Future running to wait for the real response to this request
+ } else if (bundle.getBoolean("retry")) {
+ try {
+ doWork();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ set(bundle);
+ }
+ }
+
+ @Override
+ public void onError(int code, String message) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+ || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+ // the authenticator indicated that this request was canceled or we were
+ // forbidden to fulfill; cancel now
+ cancel(true /* mayInterruptIfRunning */);
+ return;
+ }
+ setException(convertErrorToException(code, message));
+ }
+ }
+
+ }
+
+ private abstract class BaseFutureTask<T> extends FutureTask<T> {
+ final public IAccountManagerResponse mResponse;
+ final Handler mHandler;
+
+ public BaseFutureTask(Handler handler) {
+ super(new Callable<T>() {
+ @Override
+ public T call() throws Exception {
+ throw new IllegalStateException("this should never be called");
+ }
+ });
+ mHandler = handler;
+ mResponse = new Response();
+ }
+
+ public abstract void doWork() throws RemoteException;
+
+ public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException;
+
+ protected void postRunnableToHandler(Runnable runnable) {
+ Handler handler = (mHandler == null) ? mMainHandler : mHandler;
+ handler.post(runnable);
+ }
+
+ protected void startTask() {
+ try {
+ doWork();
+ } catch (RemoteException e) {
+ setException(e);
+ }
+ }
+
+ protected class Response extends IAccountManagerResponse.Stub {
+ @Override
+ public void onResult(Bundle bundle) {
+ try {
+ T result = bundleToResult(bundle);
+ if (result == null) {
+ return;
+ }
+ set(result);
+ return;
+ } catch (ClassCastException e) {
+ // we will set the exception below
+ } catch (AuthenticatorException e) {
+ // we will set the exception below
+ }
+ onError(ERROR_CODE_INVALID_RESPONSE, "no result in response");
+ }
+
+ @Override
+ public void onError(int code, String message) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+ || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+ // the authenticator indicated that this request was canceled or we were
+ // forbidden to fulfill; cancel now
+ cancel(true /* mayInterruptIfRunning */);
+ return;
+ }
+ setException(convertErrorToException(code, message));
+ }
+ }
+ }
+
+ private abstract class Future2Task<T>
+ extends BaseFutureTask<T> implements AccountManagerFuture<T> {
+ final AccountManagerCallback<T> mCallback;
+ public Future2Task(Handler handler, AccountManagerCallback<T> callback) {
+ super(handler);
+ mCallback = callback;
+ }
+
+ @Override
+ protected void done() {
+ if (mCallback != null) {
+ postRunnableToHandler(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.run(Future2Task.this);
+ }
+ });
+ }
+ }
+
+ public Future2Task<T> start() {
+ startTask();
+ return this;
+ }
+
+ private T internalGetResult(Long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
+ try {
+ if (timeout == null) {
+ return get();
+ } else {
+ return get(timeout, unit);
+ }
+ } catch (InterruptedException e) {
+ // fall through and cancel
+ } catch (TimeoutException e) {
+ // fall through and cancel
+ } catch (CancellationException e) {
+ // fall through and cancel
+ } catch (ExecutionException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ } else if (cause instanceof UnsupportedOperationException) {
+ throw new AuthenticatorException(cause);
+ } else if (cause instanceof AuthenticatorException) {
+ throw (AuthenticatorException) cause;
+ } else if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ } else {
+ throw new IllegalStateException(cause);
+ }
+ } finally {
+ cancel(true /* interrupt if running */);
+ }
+ throw new OperationCanceledException();
+ }
+
+ @Override
+ public T getResult()
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(null, null);
+ }
+
+ @Override
+ public T getResult(long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException {
+ return internalGetResult(timeout, unit);
+ }
+
+ }
+
+ private Exception convertErrorToException(int code, String message) {
+ if (code == ERROR_CODE_NETWORK_ERROR) {
+ return new IOException(message);
+ }
+
+ if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
+ return new UnsupportedOperationException(message);
+ }
+
+ if (code == ERROR_CODE_INVALID_RESPONSE) {
+ return new AuthenticatorException(message);
+ }
+
+ if (code == ERROR_CODE_BAD_ARGUMENTS) {
+ return new IllegalArgumentException(message);
+ }
+
+ return new AuthenticatorException(message);
+ }
+
+ private void getAccountByTypeAndFeatures(String accountType, String[] features,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ (new AmsTask(null, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.getAccountByTypeAndFeatures(mResponse, accountType, features,
+ mContext.getOpPackageName());
+ }
+
+ }).start();
+ }
+
+ private class GetAuthTokenByTypeAndFeaturesTask
+ extends AmsTask implements AccountManagerCallback<Bundle> {
+ GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
+ final String[] features, Activity activityForPrompting,
+ final Bundle addAccountOptions, final Bundle loginOptions,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ super(activityForPrompting, handler, callback);
+ if (accountType == null) throw new IllegalArgumentException("account type is null");
+ mAccountType = accountType;
+ mAuthTokenType = authTokenType;
+ mFeatures = features;
+ mAddAccountOptions = addAccountOptions;
+ mLoginOptions = loginOptions;
+ mMyCallback = this;
+ }
+ volatile AccountManagerFuture<Bundle> mFuture = null;
+ final String mAccountType;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final String mAuthTokenType;
+ final String[] mFeatures;
+ final Bundle mAddAccountOptions;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final Bundle mLoginOptions;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final AccountManagerCallback<Bundle> mMyCallback;
+ private volatile int mNumAccounts = 0;
+
+ @Override
+ public void doWork() throws RemoteException {
+ getAccountByTypeAndFeatures(mAccountType, mFeatures,
+ new AccountManagerCallback<Bundle>() {
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ String accountName = null;
+ String accountType = null;
+ try {
+ Bundle result = future.getResult();
+ accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+ accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ } catch (OperationCanceledException e) {
+ setException(e);
+ return;
+ } catch (IOException e) {
+ setException(e);
+ return;
+ } catch (AuthenticatorException e) {
+ setException(e);
+ return;
+ }
+
+ if (accountName == null) {
+ if (mActivity != null) {
+ // no accounts, add one now. pretend that the user directly
+ // made this request
+ mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
+ mAddAccountOptions, mActivity, mMyCallback, mHandler);
+ } else {
+ // send result since we can't prompt to add an account
+ Bundle result = new Bundle();
+ result.putString(KEY_ACCOUNT_NAME, null);
+ result.putString(KEY_ACCOUNT_TYPE, null);
+ result.putString(KEY_AUTHTOKEN, null);
+ result.putBinder(KEY_ACCOUNT_ACCESS_ID, null);
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this will never happen
+ }
+ // we are done
+ }
+ } else {
+ mNumAccounts = 1;
+ Account account = new Account(accountName, accountType);
+ // have a single account, return an authtoken for it
+ if (mActivity == null) {
+ mFuture = getAuthToken(account, mAuthTokenType,
+ false /* notifyAuthFailure */, mMyCallback, mHandler);
+ } else {
+ mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
+ mActivity, mMyCallback, mHandler);
+ }
+ }
+ }}, mHandler);
+ }
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ final Bundle result = future.getResult();
+ if (mNumAccounts == 0) {
+ final String accountName = result.getString(KEY_ACCOUNT_NAME);
+ final String accountType = result.getString(KEY_ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+ setException(new AuthenticatorException("account not in result"));
+ return;
+ }
+ final String accessId = result.getString(KEY_ACCOUNT_ACCESS_ID);
+ final Account account = new Account(accountName, accountType, accessId);
+ mNumAccounts = 1;
+ getAuthToken(account, mAuthTokenType, null /* options */, mActivity,
+ mMyCallback, mHandler);
+ return;
+ }
+ set(result);
+ } catch (OperationCanceledException e) {
+ cancel(true /* mayInterruptIfRUnning */);
+ } catch (IOException e) {
+ setException(e);
+ } catch (AuthenticatorException e) {
+ setException(e);
+ }
+ }
+ }
+
+ /**
+ * This convenience helper combines the functionality of {@link #getAccountsByTypeAndFeatures},
+ * {@link #getAuthToken}, and {@link #addAccount}.
+ *
+ * <p>
+ * This method gets a list of the accounts matching specific type and feature set which are
+ * visible to the caller (see {@link #getAccountsByType} for details);
+ * if there is exactly one already visible account, it is used; if there are some
+ * accounts for which user grant visibility, the user is prompted to pick one; if there are
+ * none, the user is prompted to add one. Finally, an auth token is acquired for the chosen
+ * account.
+ *
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * <p>
+ * <b>NOTE:</b> If targeting your app to work on API level 22 and before, MANAGE_ACCOUNTS
+ * permission is needed for those platforms. See docs for this function in API level 22.
+ *
+ * @param accountType The account type required (see {@link #getAccountsByType}), must not be
+ * null
+ * @param authTokenType The desired auth token type (see {@link #getAuthToken}), must not be
+ * null
+ * @param features Required features for the account (see
+ * {@link #getAccountsByTypeAndFeatures}), may be null or empty
+ * @param activity The {@link Activity} context to use for launching new sub-Activities to
+ * prompt to add an account, select an account, and/or enter a password, as necessary;
+ * used only to call startActivity(); should not be null
+ * @param addAccountOptions Authenticator-specific options to use for adding new accounts; may
+ * be null or empty
+ * @param getAuthTokenOptions Authenticator-specific options to use for getting auth tokens; may
+ * be null or empty
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with at least the
+ * following fields:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_NAME} - the name of the account
+ * <li>{@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li>{@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was registered for this
+ * account type or the authenticator failed to respond
+ * <li>{@link OperationCanceledException} if the operation was canceled for any reason,
+ * including the user canceling any operation
+ * <li>{@link IOException} if the authenticator experienced an I/O problem updating
+ * settings, usually because of network trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
+ final String accountType, final String authTokenType, final String[] features,
+ final Activity activity, final Bundle addAccountOptions,
+ final Bundle getAuthTokenOptions, final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("account type is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ final GetAuthTokenByTypeAndFeaturesTask task =
+ new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
+ activity, addAccountOptions, getAuthTokenOptions, callback, handler);
+ task.start();
+ return task;
+ }
+
+ /**
+ * Deprecated in favor of {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}.
+ *
+ * Returns an intent to an {@link Activity} that prompts the user to choose from a list of
+ * accounts.
+ * The caller will then typically start the activity by calling
+ * <code>startActivityForResult(intent, ...);</code>.
+ * <p>
+ * On success the activity returns a Bundle with the account name and type specified using
+ * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
+ * Chosen account is marked as {@link #VISIBILITY_USER_MANAGED_VISIBLE} to the caller
+ * (see {@link #setAccountVisibility}) and will be returned to it in consequent
+ * {@link #getAccountsByType}) calls.
+ * <p>
+ * The most common case is to call this with one account type, e.g.:
+ * <p>
+ * <pre> newChooseAccountIntent(null, null, new String[]{"com.google"}, false, null,
+ * null, null, null);</pre>
+ * @param selectedAccount if specified, indicates that the {@link Account} is the currently
+ * selected one, according to the caller's definition of selected.
+ * @param allowableAccounts an optional {@link List} of accounts that are allowed to be
+ * shown. If not specified then this field will not limit the displayed accounts.
+ * @param allowableAccountTypes an optional string array of account types. These are used
+ * both to filter the shown accounts and to filter the list of account types that are shown
+ * when adding an account. If not specified then this field will not limit the displayed
+ * account types when adding an account.
+ * @param alwaysPromptForAccount boolean that is ignored.
+ * @param descriptionOverrideText if non-null this string is used as the description in the
+ * accounts chooser screen rather than the default
+ * @param addAccountAuthTokenType this string is passed as the {@link #addAccount}
+ * authTokenType parameter
+ * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount}
+ * requiredFeatures parameter
+ * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options
+ * parameter
+ * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
+ */
+ @Deprecated
+ static public Intent newChooseAccountIntent(
+ Account selectedAccount,
+ ArrayList<Account> allowableAccounts,
+ String[] allowableAccountTypes,
+ boolean alwaysPromptForAccount,
+ String descriptionOverrideText,
+ String addAccountAuthTokenType,
+ String[] addAccountRequiredFeatures,
+ Bundle addAccountOptions) {
+ return newChooseAccountIntent(
+ selectedAccount,
+ allowableAccounts,
+ allowableAccountTypes,
+ descriptionOverrideText,
+ addAccountAuthTokenType,
+ addAccountRequiredFeatures,
+ addAccountOptions);
+ }
+
+ /**
+ * Returns an intent to an {@link Activity} that prompts the user to choose from a list of
+ * accounts.
+ * The caller will then typically start the activity by calling
+ * <code>startActivityForResult(intent, ...);</code>.
+ * <p>
+ * On success the activity returns a Bundle with the account name and type specified using
+ * keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
+ * Chosen account is marked as {@link #VISIBILITY_USER_MANAGED_VISIBLE} to the caller
+ * (see {@link #setAccountVisibility}) and will be returned to it in consequent
+ * {@link #getAccountsByType}) calls.
+ * <p>
+ * The most common case is to call this with one account type, e.g.:
+ * <p>
+ * <pre> newChooseAccountIntent(null, null, new String[]{"com.google"}, null, null, null,
+ * null);</pre>
+ * @param selectedAccount if specified, indicates that the {@link Account} is the currently
+ * selected one, according to the caller's definition of selected.
+ * @param allowableAccounts an optional {@link List} of accounts that are allowed to be
+ * shown. If not specified then this field will not limit the displayed accounts.
+ * @param allowableAccountTypes an optional string array of account types. These are used
+ * both to filter the shown accounts and to filter the list of account types that are shown
+ * when adding an account. If not specified then this field will not limit the displayed
+ * account types when adding an account.
+ * @param descriptionOverrideText if non-null this string is used as the description in the
+ * accounts chooser screen rather than the default
+ * @param addAccountAuthTokenType this string is passed as the {@link #addAccount}
+ * authTokenType parameter
+ * @param addAccountRequiredFeatures this string array is passed as the {@link #addAccount}
+ * requiredFeatures parameter
+ * @param addAccountOptions This {@link Bundle} is passed as the {@link #addAccount} options
+ * parameter
+ * @return an {@link Intent} that can be used to launch the ChooseAccount activity flow.
+ */
+ static public Intent newChooseAccountIntent(
+ Account selectedAccount,
+ List<Account> allowableAccounts,
+ String[] allowableAccountTypes,
+ String descriptionOverrideText,
+ String addAccountAuthTokenType,
+ String[] addAccountRequiredFeatures,
+ Bundle addAccountOptions) {
+ Intent intent = new Intent();
+ ComponentName componentName = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity));
+ intent.setClassName(componentName.getPackageName(),
+ componentName.getClassName());
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
+ allowableAccounts == null ? null : new ArrayList<Account>(allowableAccounts));
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+ allowableAccountTypes);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
+ addAccountOptions);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_SELECTED_ACCOUNT, selectedAccount);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_DESCRIPTION_TEXT_OVERRIDE,
+ descriptionOverrideText);
+ intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+ addAccountAuthTokenType);
+ intent.putExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+ addAccountRequiredFeatures);
+ return intent;
+ }
+
+ private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners =
+ Maps.newHashMap();
+
+ private final HashMap<OnAccountsUpdateListener, Set<String> > mAccountsUpdatedListenersTypes =
+ Maps.newHashMap();
+
+ /**
+ * BroadcastReceiver that listens for the ACTION_VISIBLE_ACCOUNTS_CHANGED intent
+ * so that it can read the updated list of accounts and send them to the listener
+ * in mAccountsUpdatedListeners.
+ */
+ private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final Account[] accounts = getAccounts();
+ // send the result to the listeners
+ synchronized (mAccountsUpdatedListeners) {
+ for (Map.Entry<OnAccountsUpdateListener, Handler> entry :
+ mAccountsUpdatedListeners.entrySet()) {
+ postToHandler(entry.getValue(), entry.getKey(), accounts);
+ }
+ }
+ }
+ };
+
+ /**
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. This
+ * listener will be notified whenever user or AbstractAccountAuthenticator made changes to
+ * accounts of any type related to the caller. This method is equivalent to
+ * addOnAccountsUpdatedListener(listener, handler, updateImmediately, null)
+ *
+ * @see #addOnAccountsUpdatedListener(OnAccountsUpdateListener, Handler, boolean,
+ * String[])
+ */
+ public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
+ Handler handler, boolean updateImmediately) {
+ addOnAccountsUpdatedListener(listener, handler,updateImmediately, null);
+ }
+
+ /**
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. This
+ * listener will be notified whenever user or AbstractAccountAuthenticator made changes to
+ * accounts of given types related to the caller -
+ * either list of accounts returned by {@link #getAccounts()}
+ * was changed, or new account was added for which user can grant access to the caller.
+ * <p>
+ * As long as this listener is present, the AccountManager instance will not be
+ * garbage-collected, and neither will the {@link Context} used to retrieve it, which may be a
+ * large Activity instance. To avoid memory leaks, you must remove this listener before then.
+ * Normally listeners are added in an Activity or Service's {@link Activity#onCreate} and
+ * removed in {@link Activity#onDestroy}.
+ * <p>
+ * It is safe to call this method from the main thread.
+ *
+ * @param listener The listener to send notifications to
+ * @param handler {@link Handler} identifying the thread to use for notifications, null for the
+ * main thread
+ * @param updateImmediately If true, the listener will be invoked (on the handler thread) right
+ * away with the current account list
+ * @param accountTypes If set, only changes to accounts of given types will be reported.
+ * @throws IllegalArgumentException if listener is null
+ * @throws IllegalStateException if listener was already added
+ */
+ public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener,
+ Handler handler, boolean updateImmediately, String[] accountTypes) {
+ if (listener == null) {
+ throw new IllegalArgumentException("the listener is null");
+ }
+ synchronized (mAccountsUpdatedListeners) {
+ if (mAccountsUpdatedListeners.containsKey(listener)) {
+ throw new IllegalStateException("this listener is already added");
+ }
+ final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
+
+ mAccountsUpdatedListeners.put(listener, handler);
+ if (accountTypes != null) {
+ mAccountsUpdatedListenersTypes.put(listener,
+ new HashSet<String>(Arrays.asList(accountTypes)));
+ } else {
+ mAccountsUpdatedListenersTypes.put(listener, null);
+ }
+
+ if (wasEmpty) {
+ // Register a broadcast receiver to monitor account changes
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_VISIBLE_ACCOUNTS_CHANGED);
+ // To recover from disk-full.
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
+ }
+
+ try {
+ // Notify AccountManagedService about new receiver.
+ // The receiver must be unregistered later exactly one time
+ mService.registerAccountListener(accountTypes, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ if (updateImmediately) {
+ postToHandler(handler, listener, getAccounts());
+ }
+ }
+
+ /**
+ * Removes an {@link OnAccountsUpdateListener} previously registered with
+ * {@link #addOnAccountsUpdatedListener}. The listener will no longer
+ * receive notifications of account changes.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param listener The previously added listener to remove
+ * @throws IllegalArgumentException if listener is null
+ * @throws IllegalStateException if listener was not already added
+ */
+ public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
+ if (listener == null) throw new IllegalArgumentException("listener is null");
+ synchronized (mAccountsUpdatedListeners) {
+ if (!mAccountsUpdatedListeners.containsKey(listener)) {
+ Log.e(TAG, "Listener was not previously added");
+ return;
+ }
+ Set<String> accountTypes = mAccountsUpdatedListenersTypes.get(listener);
+ String[] accountsArray;
+ if (accountTypes != null) {
+ accountsArray = accountTypes.toArray(new String[accountTypes.size()]);
+ } else {
+ accountsArray = null;
+ }
+ mAccountsUpdatedListeners.remove(listener);
+ mAccountsUpdatedListenersTypes.remove(listener);
+ if (mAccountsUpdatedListeners.isEmpty()) {
+ mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
+ }
+ try {
+ mService.unregisterAccountListener(accountsArray, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Asks the user to authenticate with an account of a specified type. The
+ * authenticator for this account type processes this request with the
+ * appropriate user interface. If the user does elect to authenticate with a
+ * new account, a bundle of session data for installing the account later is
+ * returned with optional account password and account status token.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ * <p>
+ * <p>
+ * <b>NOTE:</b> The account will not be installed to the device by calling
+ * this api alone. #finishSession should be called after this to install the
+ * account on device.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param options Authenticator-specific options for the request, may be
+ * null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to
+ * create an account; used only to call startActivity(); if null,
+ * the prompt will not be launched directly, but the necessary
+ * {@link Intent} will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and user was authenticated
+ * with an account:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+ * adding the the to the device later.
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If authenticator doesn't support
+ * this method, the returned Bundle contains only
+ * {@link #KEY_ACCOUNT_SESSION_BUNDLE} with encrypted
+ * {@code options} needed to add account later. If an error
+ * occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was
+ * registered for this account type or the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * creation process or adding accounts (of this type) has been
+ * disabled by policy
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem creating a new account, usually because of network
+ * trouble
+ * </ul>
+ * @see #finishSession
+ */
+ public AccountManagerFuture<Bundle> startAddAccountSession(
+ final String accountType,
+ final String authTokenType,
+ final String[] requiredFeatures,
+ final Bundle options,
+ final Activity activity,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.startAddAccountSession(
+ mResponse,
+ accountType,
+ authTokenType,
+ requiredFeatures,
+ activity != null,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Asks the user to enter a new password for the account but not updating the
+ * saved credentials for the account until {@link #finishSession} is called.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ * <p>
+ * <b>NOTE:</b> The saved credentials for the account alone will not be
+ * updated by calling this API alone. #finishSession should be called after
+ * this to update local credentials
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token of
+ * this type to be created (but no actual auth token is
+ * returned); may be null
+ * @param options Authenticator-specific options for the request; may be
+ * null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter
+ * a password; used only to call startActivity(); if null, the
+ * prompt will not be launched directly, but the necessary
+ * {@link Intent} will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if an activity was supplied and user was
+ * successfully re-authenticated to the account:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+ * updating the local credentials on device later.
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified, the returned Bundle contains
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * password prompt
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem verifying the password, usually because of network
+ * trouble
+ * </ul>
+ * @see #finishSession
+ */
+ public AccountManagerFuture<Bundle> startUpdateCredentialsSession(
+ final Account account,
+ final String authTokenType,
+ final Bundle options,
+ final Activity activity,
+ final AccountManagerCallback<Bundle> callback,
+ final Handler handler) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+
+ // Always include the calling package name. This just makes life easier
+ // down stream.
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.startUpdateCredentialsSession(
+ mResponse,
+ account,
+ authTokenType,
+ activity != null,
+ optionsIn);
+ }
+ }.start();
+ }
+
+ /**
+ * Finishes the session started by {@link #startAddAccountSession} or
+ * {@link #startUpdateCredentialsSession}. This will either add the account
+ * to AccountManager or update the local credentials stored.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * @param sessionBundle a {@link Bundle} created by {@link #startAddAccountSession} or
+ * {@link #startUpdateCredentialsSession}
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to
+ * create an account or reauthenticate existing account; used
+ * only to call startActivity(); if null, the prompt will not
+ * be launched directly, but the necessary {@link Intent} will
+ * be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if an activity was supplied and an account was added
+ * to device or local credentials were updated::
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li>{@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified and additional information is needed
+ * from user, the returned Bundle may only contain
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was
+ * registered for this account type or the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * creation process or adding accounts (of this type) has been
+ * disabled by policy
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem creating a new account, usually because of network
+ * trouble
+ * </ul>
+ * @see #startAddAccountSession and #startUpdateCredentialsSession
+ */
+ @UserHandleAware
+ public AccountManagerFuture<Bundle> finishSession(
+ final Bundle sessionBundle,
+ final Activity activity,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ return finishSessionAsUser(
+ sessionBundle,
+ activity,
+ mContext.getUser(),
+ callback,
+ handler);
+ }
+
+ /**
+ * @see #finishSession
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+ public AccountManagerFuture<Bundle> finishSessionAsUser(
+ final Bundle sessionBundle,
+ final Activity activity,
+ final UserHandle userHandle,
+ AccountManagerCallback<Bundle> callback,
+ Handler handler) {
+ if (sessionBundle == null) {
+ throw new IllegalArgumentException("sessionBundle is null");
+ }
+
+ /* Add information required by add account flow */
+ final Bundle appInfo = new Bundle();
+ appInfo.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.finishSessionAsUser(
+ mResponse,
+ sessionBundle,
+ activity != null,
+ appInfo,
+ userHandle.getIdentifier());
+ }
+ }.start();
+ }
+
+ /**
+ * Checks whether {@link #updateCredentials} or {@link #startUpdateCredentialsSession} should be
+ * called with respect to the specified account.
+ * <p>
+ * This method may be called from any thread, but the returned {@link AccountManagerFuture} must
+ * not be used on the main thread.
+ *
+ * @param account The {@link Account} to be checked whether {@link #updateCredentials} or
+ * {@link #startUpdateCredentialsSession} should be called
+ * @param statusToken a String of token to check account staus
+ * @param callback Callback to invoke when the request completes, null for no callback
+ * @param handler {@link Handler} identifying the callback thread, null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean, true if the credentials
+ * of the account should be updated.
+ */
+ public AccountManagerFuture<Boolean> isCredentialsUpdateSuggested(
+ final Account account,
+ final String statusToken,
+ AccountManagerCallback<Boolean> callback,
+ Handler handler) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+
+ if (TextUtils.isEmpty(statusToken)) {
+ throw new IllegalArgumentException("status token is empty");
+ }
+
+ return new Future2Task<Boolean>(handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.isCredentialsUpdateSuggested(
+ mResponse,
+ account,
+ statusToken);
+ }
+ @Override
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Gets whether a given package under a user has access to an account.
+ * Can be called only from the system UID.
+ *
+ * @param account The account for which to check.
+ * @param packageName The package for which to check.
+ * @param userHandle The user for which to check.
+ * @return True if the package can access the account.
+ *
+ * @hide
+ */
+ public boolean hasAccountAccess(@NonNull Account account, @NonNull String packageName,
+ @NonNull UserHandle userHandle) {
+ try {
+ return mService.hasAccountAccess(account, packageName, userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates an intent to request access to a given account for a UID.
+ * The returned intent should be stated for a result where {@link
+ * Activity#RESULT_OK} result means access was granted whereas {@link
+ * Activity#RESULT_CANCELED} result means access wasn't granted. Can
+ * be called only from the system UID.
+ *
+ * @param account The account for which to request.
+ * @param packageName The package name which to request.
+ * @param userHandle The user for which to request.
+ * @return The intent to request account access or null if the package
+ * doesn't exist.
+ *
+ * @hide
+ */
+ public IntentSender createRequestAccountAccessIntentSenderAsUser(@NonNull Account account,
+ @NonNull String packageName, @NonNull UserHandle userHandle) {
+ try {
+ return mService.createRequestAccountAccessIntentSenderAsUser(account, packageName,
+ userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Calling this will invalidate Local Accounts Data Cache which
+ * forces the next query in any process to recompute the cache
+ */
+ public static void invalidateLocalAccountsDataCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_ACCOUNTS_DATA_PROPERTY);
+ }
+
+ /**
+ * @hide
+ * Calling this will disable account data caching.
+ */
+ public void disableLocalAccountCaches() {
+ mAccountsForUserCache.disableLocal();
+ }
+
+ /**
+ * @hide
+ * Calling this will invalidate Local Account User Data Cache which
+ * forces the next query in any process to recompute the cache
+ */
+ public static void invalidateLocalAccountUserDataCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_DATA_PROPERTY);
+ }
+
+ /**
+ * @hide
+ * Calling this will disable user info caching.
+ */
+ public void disableLocalUserInfoCaches() {
+ mUserDataCache.disableLocal();
+ }
+}
diff --git a/android-34/android/accounts/AccountManagerCallback.java b/android-34/android/accounts/AccountManagerCallback.java
new file mode 100644
index 0000000..4aa7169
--- /dev/null
+++ b/android-34/android/accounts/AccountManagerCallback.java
@@ -0,0 +1,20 @@
+/*
+ * 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.accounts;
+
+public interface AccountManagerCallback<V> {
+ void run(AccountManagerFuture<V> future);
+}
\ No newline at end of file
diff --git a/android-34/android/accounts/AccountManagerFuture.java b/android-34/android/accounts/AccountManagerFuture.java
new file mode 100644
index 0000000..77670d9
--- /dev/null
+++ b/android-34/android/accounts/AccountManagerFuture.java
@@ -0,0 +1,115 @@
+/*
+ * 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.accounts;
+
+import java.util.concurrent.TimeUnit;
+import java.io.IOException;
+
+/**
+ * A <tt>AccountManagerFuture</tt> represents the result of an asynchronous
+ * {@link AccountManager} call. Methods are provided to check if the computation is
+ * complete, to wait for its completion, and to retrieve the result of
+ * the computation. The result can only be retrieved using method
+ * <tt>get</tt> when the computation has completed, blocking if
+ * necessary until it is ready. Cancellation is performed by the
+ * <tt>cancel</tt> method. Additional methods are provided to
+ * determine if the task completed normally or was cancelled. Once a
+ * computation has completed, the computation cannot be cancelled.
+ * If you would like to use a <tt>Future</tt> for the sake
+ * of cancellability but not provide a usable result, you can
+ * declare types of the form <tt>Future<?></tt> and
+ * return <tt>null</tt> as a result of the underlying task.
+ */
+public interface AccountManagerFuture<V> {
+ /**
+ * Attempts to cancel execution of this task. This attempt will
+ * fail if the task has already completed, has 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>After this method returns, subsequent calls to {@link #isDone} will
+ * always return <tt>true</tt>. Subsequent calls to {@link #isCancelled}
+ * will always return <tt>true</tt> if this method returned <tt>true</tt>.
+ *
+ * @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
+ */
+ boolean cancel(boolean mayInterruptIfRunning);
+
+ /**
+ * Returns <tt>true</tt> if this task was cancelled before it completed
+ * normally.
+ *
+ * @return <tt>true</tt> if this task was cancelled before it completed
+ */
+ boolean isCancelled();
+
+ /**
+ * Returns <tt>true</tt> if this task completed.
+ *
+ * Completion may be due to normal termination, an exception, or
+ * cancellation -- in all of these cases, this method will return
+ * <tt>true</tt>.
+ *
+ * @return <tt>true</tt> if this task completed
+ */
+ boolean isDone();
+
+ /**
+ * Accessor for the future result the {@link AccountManagerFuture} represents. This
+ * call will block until the result is available. In order to check if the result is
+ * available without blocking, one may call {@link #isDone()} and {@link #isCancelled()}.
+ * If the request that generated this result fails or is canceled then an exception
+ * will be thrown rather than the call returning normally.
+ * @return the actual result
+ * @throws android.accounts.OperationCanceledException if the request was canceled for any
+ * reason (including if it is forbidden
+ * by policy to modify an account (of that type))
+ * @throws android.accounts.AuthenticatorException if there was an error communicating with
+ * the authenticator or if the authenticator returned an invalid response
+ * @throws java.io.IOException if the authenticator returned an error response that indicates
+ * that it encountered an IOException while communicating with the authentication server
+ */
+ V getResult() throws OperationCanceledException, IOException, AuthenticatorException;
+
+ /**
+ * Accessor for the future result the {@link AccountManagerFuture} represents. This
+ * call will block until the result is available. In order to check if the result is
+ * available without blocking, one may call {@link #isDone()} and {@link #isCancelled()}.
+ * If the request that generated this result fails or is canceled then an exception
+ * will be thrown rather than the call returning normally. If a timeout is specified then
+ * the request will automatically be canceled if it does not complete in that amount of time.
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the timeout argument. This must not be null.
+ * @return the actual result
+ * @throws android.accounts.OperationCanceledException if the request was canceled for any
+ * reason
+ * @throws android.accounts.AuthenticatorException if there was an error communicating with
+ * the authenticator or if the authenticator returned an invalid response
+ * @throws java.io.IOException if the authenticator returned an error response that indicates
+ * that it encountered an IOException while communicating with the authentication server
+ */
+ V getResult(long timeout, TimeUnit unit)
+ throws OperationCanceledException, IOException, AuthenticatorException;
+}
\ No newline at end of file
diff --git a/android-34/android/accounts/AccountManagerInternal.java b/android-34/android/accounts/AccountManagerInternal.java
new file mode 100644
index 0000000..68c17c3
--- /dev/null
+++ b/android-34/android/accounts/AccountManagerInternal.java
@@ -0,0 +1,90 @@
+/*
+ * 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.accounts;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.RemoteCallback;
+
+/**
+ * Account manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AccountManagerInternal {
+
+ /**
+ * Listener for explicit UID account access grant changes.
+ */
+ public interface OnAppPermissionChangeListener {
+
+ /**
+ * Called when the explicit grant state for a given UID to
+ * access an account changes.
+ *
+ * @param account The account
+ * @param uid The UID for which the grant changed
+ */
+ public void onAppPermissionChanged(Account account, int uid);
+ }
+
+ /**
+ * Requests that a given package is given access to an account.
+ * The provided callback will be invoked with a {@link android.os.Bundle}
+ * containing the result which will be a boolean value mapped to the
+ * {@link AccountManager#KEY_BOOLEAN_RESULT} key.
+ *
+ * @param account The account for which to request.
+ * @param packageName The package name for which to request.
+ * @param userId Concrete user id for which to request.
+ * @param callback A callback for receiving the result.
+ */
+ public abstract void requestAccountAccess(@NonNull Account account,
+ @NonNull String packageName, @IntRange(from = 0) int userId,
+ @NonNull RemoteCallback callback);
+
+ /**
+ * Check whether the given UID has access to the account.
+ *
+ * @param account The account
+ * @param uid The UID
+ * @return Whether the UID can access the account
+ */
+ public abstract boolean hasAccountAccess(@NonNull Account account, @IntRange(from = 0) int uid);
+
+ /**
+ * Adds a listener for explicit UID account access grant changes.
+ *
+ * @param listener The listener
+ */
+ public abstract void addOnAppPermissionChangeListener(
+ @NonNull OnAppPermissionChangeListener listener);
+
+ /**
+ * Backups the account access permissions.
+ * @param userId The user for which to backup.
+ * @return The backup data.
+ */
+ public abstract byte[] backupAccountAccessPermissions(int userId);
+
+ /**
+ * Restores the account access permissions.
+ * @param data The restore data.
+ * @param userId The user for which to restore.
+ */
+ public abstract void restoreAccountAccessPermissions(byte[] data, int userId);
+}
diff --git a/android-34/android/accounts/AccountManagerPerfTest.java b/android-34/android/accounts/AccountManagerPerfTest.java
new file mode 100644
index 0000000..e455e6a
--- /dev/null
+++ b/android-34/android/accounts/AccountManagerPerfTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.accounts;
+
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AccountManagerPerfTest {
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testGetAccounts() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getTargetContext();
+ if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ fail("Missing required GET_ACCOUNTS permission");
+ }
+ AccountManager accountManager = AccountManager.get(context);
+ while (state.keepRunning()) {
+ accountManager.getAccounts();
+ }
+ }
+}
diff --git a/android-34/android/accounts/AccountManagerResponse.java b/android-34/android/accounts/AccountManagerResponse.java
new file mode 100644
index 0000000..369a7c3
--- /dev/null
+++ b/android-34/android/accounts/AccountManagerResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.accounts;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Used to return a response to the AccountManager.
+ * @hide
+ */
+public class AccountManagerResponse implements Parcelable {
+ private IAccountManagerResponse mResponse;
+
+ /** @hide */
+ public AccountManagerResponse(IAccountManagerResponse response) {
+ mResponse = response;
+ }
+
+ /** @hide */
+ public AccountManagerResponse(Parcel parcel) {
+ mResponse =
+ IAccountManagerResponse.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ public void onResult(Bundle result) {
+ try {
+ mResponse.onResult(result);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ try {
+ mResponse.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // this should never happen
+ }
+ }
+
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mResponse.asBinder());
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Creator<AccountManagerResponse> CREATOR =
+ new Creator<AccountManagerResponse>() {
+ public AccountManagerResponse createFromParcel(Parcel source) {
+ return new AccountManagerResponse(source);
+ }
+
+ public AccountManagerResponse[] newArray(int size) {
+ return new AccountManagerResponse[size];
+ }
+ };
+}
diff --git a/android-34/android/accounts/AccountsException.java b/android-34/android/accounts/AccountsException.java
new file mode 100644
index 0000000..b997390
--- /dev/null
+++ b/android-34/android/accounts/AccountsException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.accounts;
+
+public class AccountsException extends Exception {
+ public AccountsException() {
+ super();
+ }
+ public AccountsException(String message) {
+ super(message);
+ }
+ public AccountsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public AccountsException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/android-34/android/accounts/AuthenticatorDescription.java b/android-34/android/accounts/AuthenticatorDescription.java
new file mode 100644
index 0000000..4be3538
--- /dev/null
+++ b/android-34/android/accounts/AuthenticatorDescription.java
@@ -0,0 +1,150 @@
+/*
+ * 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.accounts;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} value type that contains information about an account authenticator.
+ */
+public class AuthenticatorDescription implements Parcelable {
+ /** The string that uniquely identifies an authenticator */
+ final public String type;
+
+ /** A resource id of a label for the authenticator that is suitable for displaying */
+ final public int labelId;
+
+ /** A resource id of a icon for the authenticator */
+ final public int iconId;
+
+ /** A resource id of a smaller icon for the authenticator */
+ final public int smallIconId;
+
+ /**
+ * A resource id for a hierarchy of PreferenceScreen to be added to the settings page for the
+ * account. See {@link AbstractAccountAuthenticator} for an example.
+ */
+ final public int accountPreferencesId;
+
+ /** The package name that can be used to lookup the resources from above. */
+ final public String packageName;
+
+ /** Authenticator handles its own token caching and permission screen */
+ final public boolean customTokens;
+
+ /** A constructor for a full AuthenticatorDescription */
+ public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+ int smallIconId, int prefId, boolean customTokens) {
+ if (type == null) throw new IllegalArgumentException("type cannot be null");
+ if (packageName == null) throw new IllegalArgumentException("packageName cannot be null");
+ this.type = type;
+ this.packageName = packageName;
+ this.labelId = labelId;
+ this.iconId = iconId;
+ this.smallIconId = smallIconId;
+ this.accountPreferencesId = prefId;
+ this.customTokens = customTokens;
+ }
+
+ public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+ int smallIconId, int prefId) {
+ this(type, packageName, labelId, iconId, smallIconId, prefId, false);
+ }
+
+ /**
+ * A factory method for creating an AuthenticatorDescription that can be used as a key
+ * to identify the authenticator by its type.
+ */
+
+ public static AuthenticatorDescription newKey(String type) {
+ if (type == null) throw new IllegalArgumentException("type cannot be null");
+ return new AuthenticatorDescription(type);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private AuthenticatorDescription(String type) {
+ this.type = type;
+ this.packageName = null;
+ this.labelId = 0;
+ this.iconId = 0;
+ this.smallIconId = 0;
+ this.accountPreferencesId = 0;
+ this.customTokens = false;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private AuthenticatorDescription(Parcel source) {
+ this.type = source.readString();
+ this.packageName = source.readString();
+ this.labelId = source.readInt();
+ this.iconId = source.readInt();
+ this.smallIconId = source.readInt();
+ this.accountPreferencesId = source.readInt();
+ this.customTokens = source.readByte() == 1;
+ }
+
+ /** @inheritDoc */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Returns the hashcode of the type only. */
+ public int hashCode() {
+ return type.hashCode();
+ }
+
+ /** Compares the type only, suitable for key comparisons. */
+ public boolean equals(@Nullable Object o) {
+ if (o == this) return true;
+ if (!(o instanceof AuthenticatorDescription)) return false;
+ final AuthenticatorDescription other = (AuthenticatorDescription) o;
+ return type.equals(other.type);
+ }
+
+ public String toString() {
+ return "AuthenticatorDescription {type=" + type + "}";
+ }
+
+ /** @inheritDoc */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(type);
+ dest.writeString(packageName);
+ dest.writeInt(labelId);
+ dest.writeInt(iconId);
+ dest.writeInt(smallIconId);
+ dest.writeInt(accountPreferencesId);
+ dest.writeByte((byte) (customTokens ? 1 : 0));
+ }
+
+ /** Used to create the object from a parcel. */
+ public static final @android.annotation.NonNull Creator<AuthenticatorDescription> CREATOR =
+ new Creator<AuthenticatorDescription>() {
+ /** @inheritDoc */
+ public AuthenticatorDescription createFromParcel(Parcel source) {
+ return new AuthenticatorDescription(source);
+ }
+
+ /** @inheritDoc */
+ public AuthenticatorDescription[] newArray(int size) {
+ return new AuthenticatorDescription[size];
+ }
+ };
+}
diff --git a/android-34/android/accounts/AuthenticatorException.java b/android-34/android/accounts/AuthenticatorException.java
new file mode 100644
index 0000000..f778d7d
--- /dev/null
+++ b/android-34/android/accounts/AuthenticatorException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.accounts;
+
+public class AuthenticatorException extends AccountsException {
+ public AuthenticatorException() {
+ super();
+ }
+ public AuthenticatorException(String message) {
+ super(message);
+ }
+ public AuthenticatorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public AuthenticatorException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/android-34/android/accounts/CantAddAccountActivity.java b/android-34/android/accounts/CantAddAccountActivity.java
new file mode 100644
index 0000000..3fac1a0
--- /dev/null
+++ b/android-34/android/accounts/CantAddAccountActivity.java
@@ -0,0 +1,51 @@
+/*
+ * 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.accounts;
+
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ * Just shows an error message about the account restrictions for the limited user.
+ */
+public class CantAddAccountActivity extends Activity {
+ public static final String EXTRA_ERROR_CODE = "android.accounts.extra.ERROR_CODE";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.app_not_authorized);
+
+ TextView view = findViewById(R.id.description);
+ String text = getSystemService(DevicePolicyManager.class).getResources().getString(
+ CANT_ADD_ACCOUNT_MESSAGE,
+ () -> getString(R.string.error_message_change_not_allowed));
+ view.setText(text);
+ }
+
+ public void onCancelButtonClicked(View view) {
+ onBackPressed();
+ }
+}
diff --git a/android-34/android/accounts/ChooseAccountActivity.java b/android-34/android/accounts/ChooseAccountActivity.java
new file mode 100644
index 0000000..20142a6
--- /dev/null
+++ b/android-34/android/accounts/ChooseAccountActivity.java
@@ -0,0 +1,216 @@
+/*
+ * 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.accounts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.HashMap;
+
+/**
+ * @hide
+ */
+public class ChooseAccountActivity extends Activity {
+
+ private static final String TAG = "AccountManager";
+
+ private Parcelable[] mAccounts = null;
+ private AccountManagerResponse mAccountManagerResponse = null;
+ private Bundle mResult;
+ private int mCallingUid;
+ private String mCallingPackage;
+
+ private HashMap<String, AuthenticatorDescription> mTypeToAuthDescription
+ = new HashMap<String, AuthenticatorDescription>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
+ mAccountManagerResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE, android.accounts.AccountManagerResponse.class);
+
+ // KEY_ACCOUNTS is a required parameter
+ if (mAccounts == null) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ mCallingUid = getLaunchedFromUid();
+ mCallingPackage = getLaunchedFromPackage();
+
+ if (UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) &&
+ getIntent().getStringExtra(AccountManager.KEY_ANDROID_PACKAGE_NAME) != null) {
+ mCallingPackage = getIntent().getStringExtra(
+ AccountManager.KEY_ANDROID_PACKAGE_NAME);
+ }
+
+ if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) &&
+ getIntent().getStringExtra(AccountManager.KEY_ANDROID_PACKAGE_NAME) != null) {
+ Log.w(getClass().getSimpleName(),
+ "Non-system Uid: " + mCallingUid + " tried to override packageName \n");
+ }
+
+ getAuthDescriptions();
+
+ AccountInfo[] mAccountInfos = new AccountInfo[mAccounts.length];
+ for (int i = 0; i < mAccounts.length; i++) {
+ mAccountInfos[i] = new AccountInfo(((Account) mAccounts[i]).name,
+ getDrawableForType(((Account) mAccounts[i]).type));
+ }
+
+ setContentView(R.layout.choose_account);
+
+ // Setup the list
+ ListView list = findViewById(android.R.id.list);
+ // Use an existing ListAdapter that will map an array of strings to TextViews
+ list.setAdapter(new AccountArrayAdapter(this,
+ android.R.layout.simple_list_item_1, mAccountInfos));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setTextFilterEnabled(true);
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ onListItemClick((ListView)parent, v, position, id);
+ }
+ });
+ }
+
+ private void getAuthDescriptions() {
+ for(AuthenticatorDescription desc : AccountManager.get(this).getAuthenticatorTypes()) {
+ mTypeToAuthDescription.put(desc.type, desc);
+ }
+ }
+
+ private Drawable getDrawableForType(String accountType) {
+ Drawable icon = null;
+ if(mTypeToAuthDescription.containsKey(accountType)) {
+ try {
+ AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+ Context authContext = createPackageContext(desc.packageName, 0);
+ icon = authContext.getDrawable(desc.iconId);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon name for account type " + accountType);
+ }
+ } catch (Resources.NotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon resource for account type " + accountType);
+ }
+ }
+ }
+ return icon;
+ }
+
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Account account = (Account) mAccounts[position];
+ // Mark account as visible since user chose it.
+ AccountManager am = AccountManager.get(this);
+ Integer oldVisibility = am.getAccountVisibility(account, mCallingPackage);
+ if (oldVisibility != null
+ && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
+ am.setAccountVisibility(account, mCallingPackage,
+ AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ }
+ Log.d(TAG, "selected account " + account.toSafeString());
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ mResult = bundle;
+ finish();
+ }
+
+ public void finish() {
+ if (mAccountManagerResponse != null) {
+ if (mResult != null) {
+ mAccountManagerResponse.onResult(mResult);
+ } else {
+ mAccountManagerResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ }
+ }
+ super.finish();
+ }
+
+ private static class AccountInfo {
+ final String name;
+ final Drawable drawable;
+
+ AccountInfo(String name, Drawable drawable) {
+ this.name = name;
+ this.drawable = drawable;
+ }
+ }
+
+ private static class ViewHolder {
+ ImageView icon;
+ TextView text;
+ }
+
+ private static class AccountArrayAdapter extends ArrayAdapter<AccountInfo> {
+ private LayoutInflater mLayoutInflater;
+ private AccountInfo[] mInfos;
+
+ public AccountArrayAdapter(Context context, int textViewResourceId, AccountInfo[] infos) {
+ super(context, textViewResourceId, infos);
+ mInfos = infos;
+ mLayoutInflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null);
+ holder = new ViewHolder();
+ holder.text = (TextView) convertView.findViewById(R.id.account_row_text);
+ holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ holder.text.setText(mInfos[position].name);
+ holder.icon.setImageDrawable(mInfos[position].drawable);
+
+ return convertView;
+ }
+ }
+}
diff --git a/android-34/android/accounts/ChooseAccountTypeActivity.java b/android-34/android/accounts/ChooseAccountTypeActivity.java
new file mode 100644
index 0000000..983dcd8
--- /dev/null
+++ b/android-34/android/accounts/ChooseAccountTypeActivity.java
@@ -0,0 +1,206 @@
+/*
+ * 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.accounts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class ChooseAccountTypeActivity extends Activity {
+ private static final String TAG = "AccountChooser";
+
+ private HashMap<String, AuthInfo> mTypeToAuthenticatorInfo = new HashMap<String, AuthInfo>();
+ private ArrayList<AuthInfo> mAuthenticatorInfosToDisplay;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseAccountTypeActivity.onCreate(savedInstanceState="
+ + savedInstanceState + ")");
+ }
+
+ // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes
+ Set<String> setOfAllowableAccountTypes = null;
+ String[] validAccountTypes = getIntent().getStringArrayExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+ if (validAccountTypes != null) {
+ setOfAllowableAccountTypes = new HashSet<String>(validAccountTypes.length);
+ for (String type : validAccountTypes) {
+ setOfAllowableAccountTypes.add(type);
+ }
+ }
+
+ // create a map of account authenticators
+ buildTypeToAuthDescriptionMap();
+
+ // Create a list of authenticators that are allowable. Filter out those that
+ // don't match the allowable account types, if provided.
+ mAuthenticatorInfosToDisplay = new ArrayList<AuthInfo>(mTypeToAuthenticatorInfo.size());
+ for (Map.Entry<String, AuthInfo> entry: mTypeToAuthenticatorInfo.entrySet()) {
+ final String type = entry.getKey();
+ final AuthInfo info = entry.getValue();
+ if (setOfAllowableAccountTypes != null
+ && !setOfAllowableAccountTypes.contains(type)) {
+ continue;
+ }
+ mAuthenticatorInfosToDisplay.add(info);
+ }
+
+ if (mAuthenticatorInfosToDisplay.isEmpty()) {
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "no allowable account types");
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ finish();
+ return;
+ }
+
+ if (mAuthenticatorInfosToDisplay.size() == 1) {
+ setResultAndFinish(mAuthenticatorInfosToDisplay.get(0).desc.type);
+ return;
+ }
+
+ setContentView(R.layout.choose_account_type);
+ // Setup the list
+ ListView list = findViewById(android.R.id.list);
+ // Use an existing ListAdapter that will map an array of strings to TextViews
+ list.setAdapter(new AccountArrayAdapter(this,
+ android.R.layout.simple_list_item_1, mAuthenticatorInfosToDisplay));
+ list.setChoiceMode(ListView.CHOICE_MODE_NONE);
+ list.setTextFilterEnabled(false);
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ setResultAndFinish(mAuthenticatorInfosToDisplay.get(position).desc.type);
+ }
+ });
+ }
+
+ private void setResultAndFinish(final String type) {
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type);
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseAccountTypeActivity.setResultAndFinish: "
+ + "selected account type " + type);
+ }
+ finish();
+ }
+
+ private void buildTypeToAuthDescriptionMap() {
+ for(AuthenticatorDescription desc : AccountManager.get(this).getAuthenticatorTypes()) {
+ String name = null;
+ Drawable icon = null;
+ try {
+ Context authContext = createPackageContext(desc.packageName, 0);
+ icon = authContext.getDrawable(desc.iconId);
+ final CharSequence sequence = authContext.getResources().getText(desc.labelId);
+ if (sequence != null) {
+ name = sequence.toString();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon name for account type " + desc.type);
+ }
+ } catch (Resources.NotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon resource for account type " + desc.type);
+ }
+ }
+ AuthInfo authInfo = new AuthInfo(desc, name, icon);
+ mTypeToAuthenticatorInfo.put(desc.type, authInfo);
+ }
+ }
+
+ private static class AuthInfo {
+ final AuthenticatorDescription desc;
+ final String name;
+ final Drawable drawable;
+
+ AuthInfo(AuthenticatorDescription desc, String name, Drawable drawable) {
+ this.desc = desc;
+ this.name = name;
+ this.drawable = drawable;
+ }
+ }
+
+ private static class ViewHolder {
+ ImageView icon;
+ TextView text;
+ }
+
+ private static class AccountArrayAdapter extends ArrayAdapter<AuthInfo> {
+ private LayoutInflater mLayoutInflater;
+ private ArrayList<AuthInfo> mInfos;
+
+ public AccountArrayAdapter(Context context, int textViewResourceId,
+ ArrayList<AuthInfo> infos) {
+ super(context, textViewResourceId, infos);
+ mInfos = infos;
+ mLayoutInflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null);
+ holder = new ViewHolder();
+ holder.text = (TextView) convertView.findViewById(R.id.account_row_text);
+ holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ holder.text.setText(mInfos.get(position).name);
+ holder.icon.setImageDrawable(mInfos.get(position).drawable);
+
+ return convertView;
+ }
+ }
+}
diff --git a/android-34/android/accounts/ChooseTypeAndAccountActivity.java b/android-34/android/accounts/ChooseTypeAndAccountActivity.java
new file mode 100644
index 0000000..e447d86
--- /dev/null
+++ b/android-34/android/accounts/ChooseTypeAndAccountActivity.java
@@ -0,0 +1,624 @@
+/*
+ * 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.accounts;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import com.google.android.collect.Sets;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class ChooseTypeAndAccountActivity extends Activity
+ implements AccountManagerCallback<Bundle> {
+ private static final String TAG = "AccountChooser";
+
+ /**
+ * A Parcelable ArrayList of Account objects that limits the choosable accounts to those
+ * in this list, if this parameter is supplied.
+ */
+ public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts";
+
+ /**
+ * A Parcelable ArrayList of String objects that limits the accounts to choose to those
+ * that match the types in this list, if this parameter is supplied. This list is also
+ * used to filter the allowable account types if add account is selected.
+ */
+ public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes";
+
+ /**
+ * This is passed as the addAccountOptions parameter in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions";
+
+ /**
+ * This is passed as the requiredFeatures parameter in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY =
+ "addAccountRequiredFeatures";
+
+ /**
+ * This is passed as the authTokenType string in AccountManager.addAccount()
+ * if it is called.
+ */
+ public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType";
+
+ /**
+ * If set then the specified account is already "selected".
+ */
+ public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
+
+ /**
+ * Deprecated. Providing this extra to {@link ChooseTypeAndAccountActivity}
+ * will have no effect.
+ */
+ @Deprecated
+ public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT =
+ "alwaysPromptForAccount";
+
+ /**
+ * If set then this string will be used as the description rather than
+ * the default.
+ */
+ public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride";
+
+ public static final int REQUEST_NULL = 0;
+ public static final int REQUEST_CHOOSE_TYPE = 1;
+ public static final int REQUEST_ADD_ACCOUNT = 2;
+
+ private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest";
+ private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
+ private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
+ private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
+ private static final String KEY_INSTANCE_STATE_ACCOUNTS_LIST = "accountsList";
+ private static final String KEY_INSTANCE_STATE_VISIBILITY_LIST = "visibilityList";
+
+ private static final int SELECTED_ITEM_NONE = -1;
+
+ private Set<Account> mSetOfAllowableAccounts;
+ private Set<String> mSetOfRelevantAccountTypes;
+ private String mSelectedAccountName = null;
+ private boolean mSelectedAddNewAccount = false;
+ private String mDescriptionOverride;
+
+ private LinkedHashMap<Account, Integer> mAccounts;
+ // TODO Redesign flow to show NOT_VISIBLE accounts
+ // and display a warning if they are selected.
+ // Currently NOT_VISBILE accounts are not shown at all.
+ private ArrayList<Account> mPossiblyVisibleAccounts;
+ private int mPendingRequest = REQUEST_NULL;
+ private Parcelable[] mExistingAccounts = null;
+ private int mSelectedItemIndex;
+ private Button mOkButton;
+ private int mCallingUid;
+ private String mCallingPackage;
+ private boolean mDisallowAddAccounts;
+ private boolean mDontShowPicker;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState="
+ + savedInstanceState + ")");
+ }
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ mCallingUid = getLaunchedFromUid();
+ mCallingPackage = getLaunchedFromPackage();
+ if (mCallingUid != 0 && mCallingPackage != null) {
+ Bundle restrictions = UserManager.get(this)
+ .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid)));
+ mDisallowAddAccounts =
+ restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false);
+ }
+
+ // save some items we use frequently
+ final Intent intent = getIntent();
+
+ mSetOfAllowableAccounts = getAllowableAccountSet(intent);
+ mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
+ mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+
+ if (savedInstanceState != null) {
+ mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
+ mExistingAccounts =
+ savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
+
+ // Makes sure that any user selection is preserved across orientation changes.
+ mSelectedAccountName =
+ savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
+ mSelectedAddNewAccount =
+ savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ // restore mAccounts
+ Parcelable[] accounts =
+ savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST);
+ ArrayList<Integer> visibility =
+ savedInstanceState.getIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST);
+ mAccounts = new LinkedHashMap<>();
+ for (int i = 0; i < accounts.length; i++) {
+ mAccounts.put((Account) accounts[i], visibility.get(i));
+ }
+ } else {
+ mPendingRequest = REQUEST_NULL;
+ mExistingAccounts = null;
+ // If the selected account as specified in the intent matches one in the list we will
+ // show is as pre-selected.
+ Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT, android.accounts.Account.class);
+ if (selectedAccount != null) {
+ mSelectedAccountName = selectedAccount.name;
+ }
+ mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
+ }
+
+ mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
+ for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
+ if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
+ mPossiblyVisibleAccounts.add(entry.getKey());
+ }
+ }
+
+ if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ setContentView(R.layout.app_not_authorized);
+ TextView view = findViewById(R.id.description);
+ String text = getSystemService(DevicePolicyManager.class).getResources().getString(
+ CANT_ADD_ACCOUNT_MESSAGE,
+ () -> getString(R.string.error_message_change_not_allowed));
+ view.setText(text);
+
+ mDontShowPicker = true;
+ }
+
+ if (mDontShowPicker) {
+ super.onCreate(savedInstanceState);
+ return;
+ }
+
+ // In cases where the activity does not need to show an account picker, cut the chase
+ // and return the result directly. Eg:
+ // Single account -> select it directly
+ // No account -> launch add account activity directly
+ if (mPendingRequest == REQUEST_NULL) {
+ // If there are no relevant accounts and only one relevant account type go directly to
+ // add account. Otherwise let the user choose.
+ if (mPossiblyVisibleAccounts.isEmpty()) {
+ setNonLabelThemeAndCallSuperCreate(savedInstanceState);
+ if (mSetOfRelevantAccountTypes.size() == 1) {
+ runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
+ } else {
+ startChooseAccountTypeActivity();
+ }
+ }
+ }
+
+ String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
+ mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
+ mSelectedAddNewAccount);
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.choose_type_and_account);
+ overrideDescriptionIfSupplied(mDescriptionOverride);
+ populateUIAccountList(listItems);
+
+ // Only enable "OK" button if something has been selected.
+ mOkButton = findViewById(android.R.id.button2);
+ mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()");
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest);
+ if (mPendingRequest == REQUEST_ADD_ACCOUNT) {
+ outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
+ }
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
+ outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
+ } else {
+ outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
+ mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
+ }
+ }
+ // save mAccounts
+ Parcelable[] accounts = new Parcelable[mAccounts.size()];
+ ArrayList<Integer> visibility = new ArrayList<>(mAccounts.size());
+ int i = 0;
+ for (Map.Entry<Account, Integer> e : mAccounts.entrySet()) {
+ accounts[i++] = e.getKey();
+ visibility.add(e.getValue());
+ }
+ outState.putParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST, accounts);
+ outState.putIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST, visibility);
+ }
+
+ public void onCancelButtonClicked(View view) {
+ onBackPressed();
+ }
+
+ public void onOkButtonClicked(View view) {
+ if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
+ // Selected "Add New Account" option
+ startChooseAccountTypeActivity();
+ } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ onAccountSelected(mPossiblyVisibleAccounts.get(mSelectedItemIndex));
+ }
+ }
+
+ // Called when the choose account type activity (for adding an account) returns.
+ // If it was a success read the account and set it in the result. In all cases
+ // return the result and finish this activity.
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode,
+ final Intent data) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (data != null && data.getExtras() != null) data.getExtras().keySet();
+ Bundle extras = data != null ? data.getExtras() : null;
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode
+ + ", resCode=" + resultCode + ")");
+ }
+
+ // we got our result, so clear the fact that we had a pending request
+ mPendingRequest = REQUEST_NULL;
+
+ if (resultCode == RESULT_CANCELED) {
+ // if canceling out of addAccount and the original state caused us to skip this,
+ // finish this activity
+ if (mPossiblyVisibleAccounts.isEmpty()) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ return;
+ }
+
+ if (resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_CHOOSE_TYPE) {
+ if (data != null) {
+ String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+ if (accountType != null) {
+ runAddAccountForAuthenticator(accountType);
+ return;
+ }
+ }
+ Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account "
+ + "type, pretending the request was canceled");
+ } else if (requestCode == REQUEST_ADD_ACCOUNT) {
+ String accountName = null;
+ String accountType = null;
+
+ if (data != null) {
+ accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
+ accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+ }
+
+ if (accountName == null || accountType == null) {
+ // new account was added.
+ Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
+ mCallingPackage, mCallingUid);
+ Set<Account> preExistingAccounts = new HashSet<Account>();
+ for (Parcelable accountParcel : mExistingAccounts) {
+ preExistingAccounts.add((Account) accountParcel);
+ }
+ for (Account account : currentAccounts) {
+ // New account is visible to the app - return it.
+ if (!preExistingAccounts.contains(account)) {
+ accountName = account.name;
+ accountType = account.type;
+ break;
+ }
+ }
+ }
+
+ if (accountName != null || accountType != null) {
+ setResultAndFinish(accountName, accountType);
+ return;
+ }
+ }
+ Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added "
+ + "account, pretending the request was canceled");
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled");
+ }
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+
+ protected void runAddAccountForAuthenticator(String type) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runAddAccountForAuthenticator: " + type);
+ }
+ final Bundle options = getIntent().getBundleExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE);
+ final String[] requiredFeatures = getIntent().getStringArrayExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY);
+ final String authTokenType = getIntent().getStringExtra(
+ ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING);
+ AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures,
+ options, null /* activity */, this /* callback */, null /* Handler */);
+ }
+
+ @Override
+ public void run(final AccountManagerFuture<Bundle> accountManagerFuture) {
+ try {
+ final Bundle accountManagerResult = accountManagerFuture.getResult();
+ final Intent intent = (Intent)accountManagerResult.getParcelable(
+ AccountManager.KEY_INTENT, android.content.Intent.class);
+ if (intent != null) {
+ mPendingRequest = REQUEST_ADD_ACCOUNT;
+ mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
+ mCallingUid);
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
+ return;
+ }
+ } catch (OperationCanceledException e) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ } catch (IOException e) {
+ } catch (AuthenticatorException e) {
+ }
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server");
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ finish();
+ }
+
+ /**
+ * The default activity theme shows label at the top. Set a theme which does
+ * not show label, which effectively makes the activity invisible. Note that
+ * no content is being set. If something gets set, using this theme may be
+ * useless.
+ */
+ private void setNonLabelThemeAndCallSuperCreate(Bundle savedInstanceState) {
+ setTheme(R.style.Theme_DeviceDefault_Light_Dialog_NoActionBar);
+ super.onCreate(savedInstanceState);
+ }
+
+ private void onAccountSelected(Account account) {
+ Log.d(TAG, "selected account " + account.toSafeString());
+ setResultAndFinish(account.name, account.type);
+ }
+
+ private void setResultAndFinish(final String accountName, final String accountType) {
+ // Mark account as visible since user chose it.
+ Account account = new Account(accountName, accountType);
+ Integer oldVisibility =
+ AccountManager.get(this).getAccountVisibility(account, mCallingPackage);
+ if (oldVisibility != null
+ && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
+ AccountManager.get(this).setAccountVisibility(account, mCallingPackage,
+ AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+ }
+
+ if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_NOT_VISIBLE) {
+ // Added account is not visible to caller.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
+ bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+ setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
+ + account.toSafeString());
+ }
+ finish();
+ }
+
+ private void startChooseAccountTypeActivity() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()");
+ }
+ final Intent intent = new Intent(this, ChooseAccountTypeActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+ getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
+ getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+ getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY));
+ intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+ getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING));
+ startActivityForResult(intent, REQUEST_CHOOSE_TYPE);
+ mPendingRequest = REQUEST_CHOOSE_TYPE;
+ }
+
+ /**
+ * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE.
+ * An index value of accounts.size() indicates 'Add account' option.
+ */
+ private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName,
+ boolean selectedAddNewAccount) {
+ // If "Add account" option was previously selected by user, preserve it across
+ // orientation changes.
+ if (selectedAddNewAccount) {
+ return accounts.size();
+ }
+ // search for the selected account name if present
+ for (int i = 0; i < accounts.size(); i++) {
+ if (accounts.get(i).name.equals(selectedAccountName)) {
+ return i;
+ }
+ }
+ // no account selected.
+ return SELECTED_ITEM_NONE;
+ }
+
+ private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) {
+ // List of options includes all accounts found together with "Add new account" as the
+ // last item in the list.
+ String[] listItems = new String[accounts.size() + (mDisallowAddAccounts ? 0 : 1)];
+ for (int i = 0; i < accounts.size(); i++) {
+ listItems[i] = accounts.get(i).name;
+ }
+ if (!mDisallowAddAccounts) {
+ listItems[accounts.size()] = getResources().getString(
+ R.string.add_account_button_label);
+ }
+ return listItems;
+ }
+
+ /**
+ * Create a list of Account objects for each account that is acceptable. Filter out accounts
+ * that don't match the allowable types, if provided, or that don't match the allowable
+ * accounts, if provided.
+ */
+ private LinkedHashMap<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
+ Map<Account, Integer> accountsAndVisibilityForCaller =
+ accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null);
+ Account[] allAccounts = accountManager.getAccounts();
+ LinkedHashMap<Account, Integer> accountsToPopulate =
+ new LinkedHashMap<>(accountsAndVisibilityForCaller.size());
+ for (Account account : allAccounts) {
+ if (mSetOfAllowableAccounts != null
+ && !mSetOfAllowableAccounts.contains(account)) {
+ continue;
+ }
+ if (mSetOfRelevantAccountTypes != null
+ && !mSetOfRelevantAccountTypes.contains(account.type)) {
+ continue;
+ }
+ if (accountsAndVisibilityForCaller.get(account) != null) {
+ accountsToPopulate.put(account, accountsAndVisibilityForCaller.get(account));
+ }
+ }
+ return accountsToPopulate;
+ }
+
+ /**
+ * Return a set of account types specified by the intent as well as supported by the
+ * AccountManager.
+ */
+ private Set<String> getReleventAccountTypes(final Intent intent) {
+ // An account type is relevant iff it is allowed by the caller and supported by the account
+ // manager.
+ Set<String> setOfRelevantAccountTypes = null;
+ final String[] allowedAccountTypes =
+ intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+ AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
+ Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
+ for (AuthenticatorDescription desc : descs) {
+ supportedAccountTypes.add(desc.type);
+ }
+ if (allowedAccountTypes != null) {
+ setOfRelevantAccountTypes = Sets.newHashSet(allowedAccountTypes);
+ setOfRelevantAccountTypes.retainAll(supportedAccountTypes);
+ } else {
+ setOfRelevantAccountTypes = supportedAccountTypes;
+ }
+ return setOfRelevantAccountTypes;
+ }
+
+ /**
+ * Returns a set of allowlisted accounts given by the intent or null if none specified by the
+ * intent.
+ */
+ private Set<Account> getAllowableAccountSet(final Intent intent) {
+ Set<Account> setOfAllowableAccounts = null;
+ final ArrayList<Parcelable> validAccounts =
+ intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
+ if (validAccounts != null) {
+ setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
+ for (Parcelable parcelable : validAccounts) {
+ setOfAllowableAccounts.add((Account)parcelable);
+ }
+ }
+ return setOfAllowableAccounts;
+ }
+
+ /**
+ * Overrides the description text view for the picker activity if specified by the intent.
+ * If not specified then makes the description invisible.
+ */
+ private void overrideDescriptionIfSupplied(String descriptionOverride) {
+ TextView descriptionView = findViewById(R.id.description);
+ if (!TextUtils.isEmpty(descriptionOverride)) {
+ descriptionView.setText(descriptionOverride);
+ } else {
+ descriptionView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Populates the UI ListView with the given list of items and selects an item
+ * based on {@code mSelectedItemIndex} member variable.
+ */
+ private final void populateUIAccountList(String[] listItems) {
+ ListView list = findViewById(android.R.id.list);
+ list.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_single_choice, listItems));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setItemsCanFocus(false);
+ list.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ mSelectedItemIndex = position;
+ mOkButton.setEnabled(true);
+ }
+ });
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ list.setItemChecked(mSelectedItemIndex, true);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
+ }
+ }
+ }
+}
diff --git a/android-34/android/accounts/GrantCredentialsPermissionActivity.java b/android-34/android/accounts/GrantCredentialsPermissionActivity.java
new file mode 100644
index 0000000..a89fae7
--- /dev/null
+++ b/android-34/android/accounts/GrantCredentialsPermissionActivity.java
@@ -0,0 +1,213 @@
+/*
+ * 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.accounts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.io.IOException;
+
+/**
+ * @hide
+ */
+public class GrantCredentialsPermissionActivity extends Activity implements View.OnClickListener {
+ public static final String EXTRAS_ACCOUNT = "account";
+ public static final String EXTRAS_AUTH_TOKEN_TYPE = "authTokenType";
+ public static final String EXTRAS_RESPONSE = "response";
+ public static final String EXTRAS_REQUESTING_UID = "uid";
+
+ private Account mAccount;
+ private String mAuthTokenType;
+ private int mUid;
+ private int mCallingUid;
+ private Bundle mResultBundle = null;
+ protected LayoutInflater mInflater;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ android.view.WindowManager.LayoutParams
+ .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ setContentView(R.layout.grant_credentials_permission);
+ setTitle(R.string.grant_permissions_header_text);
+
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ // Grant 'account'/'type' to mUID
+ mAccount = extras.getParcelable(EXTRAS_ACCOUNT, android.accounts.Account.class);
+ mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+ mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+ final PackageManager pm = getPackageManager();
+ final String[] packages = pm.getPackagesForUid(mUid);
+
+ if (mAccount == null || mAuthTokenType == null || packages == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ mCallingUid = getLaunchedFromUid();
+
+ if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) && mCallingUid != mUid) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ String accountTypeLabel;
+ try {
+ accountTypeLabel = getAccountLabel(mAccount);
+ } catch (IllegalArgumentException e) {
+ // label or resource was missing. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ final TextView authTokenTypeView = findViewById(R.id.authtoken_type);
+ authTokenTypeView.setVisibility(View.GONE);
+
+ final AccountManagerCallback<String> callback = new AccountManagerCallback<String>() {
+ public void run(AccountManagerFuture<String> future) {
+ try {
+ final String authTokenLabel = future.getResult();
+ if (!TextUtils.isEmpty(authTokenLabel)) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (!isFinishing()) {
+ authTokenTypeView.setText(authTokenLabel);
+ authTokenTypeView.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+ } catch (OperationCanceledException e) {
+ } catch (IOException e) {
+ } catch (AuthenticatorException e) {
+ }
+ }
+ };
+
+ if (!AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE.equals(mAuthTokenType)) {
+ AccountManager.get(this).getAuthTokenLabel(mAccount.type,
+ mAuthTokenType, callback, null);
+ }
+
+ findViewById(R.id.allow_button).setOnClickListener(this);
+ findViewById(R.id.deny_button).setOnClickListener(this);
+
+ LinearLayout packagesListView = findViewById(R.id.packages_list);
+
+ for (String pkg : packages) {
+ String packageLabel;
+ try {
+ packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ packageLabel = pkg;
+ }
+ packagesListView.addView(newPackageView(packageLabel));
+ }
+
+ ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+ ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+ }
+
+ private String getAccountLabel(Account account) {
+ final AuthenticatorDescription[] authenticatorTypes =
+ AccountManager.get(this).getAuthenticatorTypes();
+ for (int i = 0, N = authenticatorTypes.length; i < N; i++) {
+ final AuthenticatorDescription desc = authenticatorTypes[i];
+ if (desc.type.equals(account.type)) {
+ try {
+ return createPackageContext(desc.packageName, 0).getString(desc.labelId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return account.type;
+ } catch (Resources.NotFoundException e) {
+ return account.type;
+ }
+ }
+ }
+ return account.type;
+ }
+
+ private View newPackageView(String packageLabel) {
+ View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+ ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+ return view;
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.allow_button:
+ AccountManager.get(this).updateAppPermission(mAccount, mAuthTokenType, mUid, true);
+ Intent result = new Intent();
+ result.putExtra("retry", true);
+ setResult(RESULT_OK, result);
+ setAccountAuthenticatorResult(result.getExtras());
+ break;
+
+ case R.id.deny_button:
+ AccountManager.get(this).updateAppPermission(mAccount, mAuthTokenType, mUid, false);
+ setResult(RESULT_CANCELED);
+ break;
+ }
+ finish();
+ }
+
+ public final void setAccountAuthenticatorResult(Bundle result) {
+ mResultBundle = result;
+ }
+
+ /**
+ * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+ * result isn't present.
+ */
+ public void finish() {
+ Intent intent = getIntent();
+ AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE, android.accounts.AccountAuthenticatorResponse.class);
+ if (response != null) {
+ // send the result bundle back if set, otherwise send an error.
+ if (mResultBundle != null) {
+ response.onResult(mResultBundle);
+ } else {
+ response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ }
+ }
+ super.finish();
+ }
+}
diff --git a/android-34/android/accounts/NetworkErrorException.java b/android-34/android/accounts/NetworkErrorException.java
new file mode 100644
index 0000000..07f4ce9
--- /dev/null
+++ b/android-34/android/accounts/NetworkErrorException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.accounts;
+
+public class NetworkErrorException extends AccountsException {
+ public NetworkErrorException() {
+ super();
+ }
+ public NetworkErrorException(String message) {
+ super(message);
+ }
+ public NetworkErrorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public NetworkErrorException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/android-34/android/accounts/OnAccountsUpdateListener.java b/android-34/android/accounts/OnAccountsUpdateListener.java
new file mode 100644
index 0000000..2b4ee50
--- /dev/null
+++ b/android-34/android/accounts/OnAccountsUpdateListener.java
@@ -0,0 +1,29 @@
+/*
+ * 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.accounts;
+
+/**
+ * An interface that contains the callback used by the AccountManager
+ */
+public interface OnAccountsUpdateListener {
+ /**
+ * This invoked when the AccountManager starts up and whenever the account
+ * set changes.
+ * @param accounts the current accounts
+ */
+ void onAccountsUpdated(Account[] accounts);
+}
diff --git a/android-34/android/accounts/OperationCanceledException.java b/android-34/android/accounts/OperationCanceledException.java
new file mode 100644
index 0000000..896d194
--- /dev/null
+++ b/android-34/android/accounts/OperationCanceledException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.accounts;
+
+public class OperationCanceledException extends AccountsException {
+ public OperationCanceledException() {
+ super();
+ }
+ public OperationCanceledException(String message) {
+ super(message);
+ }
+ public OperationCanceledException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ public OperationCanceledException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/android-34/android/animation/AnimationHandler.java b/android-34/android/animation/AnimationHandler.java
new file mode 100644
index 0000000..df8a50a
--- /dev/null
+++ b/android-34/android/animation/AnimationHandler.java
@@ -0,0 +1,500 @@
+/*
+ * 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.animation;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.Choreographer;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This custom, static handler handles the timing pulse that is shared by all active
+ * ValueAnimators. This approach ensures that the setting of animation values will happen on the
+ * same thread that animations start on, and that all animations will share the same times for
+ * calculating their values, which makes synchronizing animations possible.
+ *
+ * The handler uses the Choreographer by default for doing periodic callbacks. A custom
+ * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
+ * may be independent of UI frame update. This could be useful in testing.
+ *
+ * @hide
+ */
+public class AnimationHandler {
+
+ private static final String TAG = "AnimationHandler";
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * Internal per-thread collections used to avoid set collisions as animations start and end
+ * while being processed.
+ */
+ private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
+ new ArrayMap<>();
+ private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
+ new ArrayList<>();
+ private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
+ new ArrayList<>();
+ private AnimationFrameCallbackProvider mProvider;
+
+ // Static flag which allows the pausing behavior to be globally disabled/enabled.
+ private static boolean sAnimatorPausingEnabled = isPauseBgAnimationsEnabledInSystemProperties();
+
+ // Static flag which prevents the system property from overriding sAnimatorPausingEnabled field.
+ private static boolean sOverrideAnimatorPausingSystemProperty = false;
+
+ /**
+ * This paused list is used to store animators forcibly paused when the activity
+ * went into the background (to avoid unnecessary background processing work).
+ * These animators should be resume()'d when the activity returns to the foreground.
+ */
+ private final ArrayList<Animator> mPausedAnimators = new ArrayList<>();
+
+ /**
+ * This structure is used to store the currently active objects (ViewRootImpls or
+ * WallpaperService.Engines) in the process. Each of these objects sends a request to
+ * AnimationHandler when it goes into the background (request to pause) or foreground
+ * (request to resume). Because all animators are managed by AnimationHandler on the same
+ * thread, it should only ever pause animators when *all* requestors are in the background.
+ * This list tracks the background/foreground state of all requestors and only ever
+ * pauses animators when all items are in the background (false). To simplify, we only ever
+ * store visible (foreground) requestors; if the set size reaches zero, there are no
+ * objects in the foreground and it is time to pause animators.
+ */
+ private final ArrayList<WeakReference<Object>> mAnimatorRequestors = new ArrayList<>();
+
+ private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ doAnimationFrame(getProvider().getFrameTime());
+ if (mAnimationCallbacks.size() > 0) {
+ getProvider().postFrameCallback(this);
+ }
+ }
+ };
+
+ public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
+ private boolean mListDirty = false;
+
+ public static AnimationHandler getInstance() {
+ if (sAnimatorHandler.get() == null) {
+ sAnimatorHandler.set(new AnimationHandler());
+ }
+ return sAnimatorHandler.get();
+ }
+
+ /**
+ * System property that controls the behavior of pausing infinite animators when an app
+ * is moved to the background.
+ *
+ * @return the value of 'framework.pause_bg_animations.enabled' system property
+ */
+ private static boolean isPauseBgAnimationsEnabledInSystemProperties() {
+ if (sOverrideAnimatorPausingSystemProperty) return sAnimatorPausingEnabled;
+ return SystemProperties
+ .getBoolean("framework.pause_bg_animations.enabled", true);
+ }
+
+ /**
+ * Disable the default behavior of pausing infinite animators when
+ * apps go into the background.
+ *
+ * @param enable Enable (default behavior) or disable background pausing behavior.
+ */
+ public static void setAnimatorPausingEnabled(boolean enable) {
+ sAnimatorPausingEnabled = enable;
+ }
+
+ /**
+ * Prevents the setAnimatorPausingEnabled behavior from being overridden
+ * by the 'framework.pause_bg_animations.enabled' system property value.
+ *
+ * This is for testing purposes only.
+ *
+ * @param enable Enable or disable (default behavior) overriding the system
+ * property.
+ */
+ public static void setOverrideAnimatorPausingSystemProperty(boolean enable) {
+ sOverrideAnimatorPausingSystemProperty = enable;
+ }
+
+ /**
+ * This is called when a window goes away. We should remove
+ * it from the requestors list to ensure that we are counting requests correctly and not
+ * tracking obsolete+enabled requestors.
+ */
+ public static void removeRequestor(Object requestor) {
+ getInstance().requestAnimatorsEnabledImpl(false, requestor);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "removeRequestor for " + requestor);
+ }
+ }
+
+ /**
+ * This method is called from ViewRootImpl or WallpaperService when either a window is no
+ * longer visible (enable == false) or when a window becomes visible (enable == true).
+ * If animators are not properly disabled when activities are backgrounded, it can lead to
+ * unnecessary processing, particularly for infinite animators, as the system will continue
+ * to pulse timing events even though the results are not visible. As a workaround, we
+ * pause all un-paused infinite animators, and resume them when any window in the process
+ * becomes visible.
+ */
+ public static void requestAnimatorsEnabled(boolean enable, Object requestor) {
+ getInstance().requestAnimatorsEnabledImpl(enable, requestor);
+ }
+
+ private void requestAnimatorsEnabledImpl(boolean enable, Object requestor) {
+ boolean wasEmpty = mAnimatorRequestors.isEmpty();
+ setAnimatorPausingEnabled(isPauseBgAnimationsEnabledInSystemProperties());
+ synchronized (mAnimatorRequestors) {
+ // Only store WeakRef objects to avoid leaks
+ if (enable) {
+ // First, check whether such a reference is already on the list
+ WeakReference<Object> weakRef = null;
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor) {
+ weakRef = ref;
+ } else if (referent == null) {
+ // Remove any reference that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ if (weakRef == null) {
+ weakRef = new WeakReference<>(requestor);
+ mAnimatorRequestors.add(weakRef);
+ }
+ } else {
+ for (int i = mAnimatorRequestors.size() - 1; i >= 0; --i) {
+ WeakReference<Object> ref = mAnimatorRequestors.get(i);
+ Object referent = ref.get();
+ if (referent == requestor || referent == null) {
+ // remove requested item or item that has been cleared
+ mAnimatorRequestors.remove(i);
+ }
+ }
+ // If a reference to the requestor wasn't in the list, nothing to remove
+ }
+ }
+ if (!sAnimatorPausingEnabled) {
+ // Resume any animators that have been paused in the meantime, otherwise noop
+ // Leave logic above so that if pausing gets re-enabled, the state of the requestors
+ // list is valid
+ resumeAnimators();
+ return;
+ }
+ boolean isEmpty = mAnimatorRequestors.isEmpty();
+ if (wasEmpty != isEmpty) {
+ // only paused/resume animators if there was a visibility change
+ if (!isEmpty) {
+ // If any requestors are enabled, resume currently paused animators
+ resumeAnimators();
+ } else {
+ // Wait before pausing to avoid thrashing animator state for temporary backgrounding
+ Choreographer.getInstance().postFrameCallbackDelayed(mPauser,
+ Animator.getBackgroundPauseDelay());
+ }
+ }
+ if (LOCAL_LOGV) {
+ Log.v(TAG, (enable ? "enable" : "disable") + " animators for " + requestor
+ + " with pauseDelay of " + Animator.getBackgroundPauseDelay());
+ for (int i = 0; i < mAnimatorRequestors.size(); ++i) {
+ Log.v(TAG, "animatorRequestors " + i + " = "
+ + mAnimatorRequestors.get(i) + " with referent "
+ + mAnimatorRequestors.get(i).get());
+ }
+ }
+ }
+
+ private void resumeAnimators() {
+ Choreographer.getInstance().removeFrameCallback(mPauser);
+ for (int i = mPausedAnimators.size() - 1; i >= 0; --i) {
+ mPausedAnimators.get(i).resume();
+ }
+ mPausedAnimators.clear();
+ }
+
+ private Choreographer.FrameCallback mPauser = frameTimeNanos -> {
+ if (mAnimatorRequestors.size() > 0) {
+ // something enabled animators since this callback was scheduled - bail
+ return;
+ }
+ for (int i = 0; i < mAnimationCallbacks.size(); ++i) {
+ AnimationFrameCallback callback = mAnimationCallbacks.get(i);
+ if (callback instanceof Animator) {
+ Animator animator = ((Animator) callback);
+ if (animator.getTotalDuration() == Animator.DURATION_INFINITE
+ && !animator.isPaused()) {
+ mPausedAnimators.add(animator);
+ animator.pause();
+ }
+ }
+ }
+ };
+
+ /**
+ * By default, the Choreographer is used to provide timing for frame callbacks. A custom
+ * provider can be used here to provide different timing pulse.
+ */
+ public void setProvider(AnimationFrameCallbackProvider provider) {
+ if (provider == null) {
+ mProvider = new MyFrameCallbackProvider();
+ } else {
+ mProvider = provider;
+ }
+ }
+
+ private AnimationFrameCallbackProvider getProvider() {
+ if (mProvider == null) {
+ mProvider = new MyFrameCallbackProvider();
+ }
+ return mProvider;
+ }
+
+ /**
+ * Register to get a callback on the next frame after the delay.
+ */
+ public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
+ if (mAnimationCallbacks.size() == 0) {
+ getProvider().postFrameCallback(mFrameCallback);
+ }
+ if (!mAnimationCallbacks.contains(callback)) {
+ mAnimationCallbacks.add(callback);
+ }
+
+ if (delay > 0) {
+ mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
+ }
+ }
+
+ /**
+ * Register to get a one shot callback for frame commit timing. Frame commit timing is the
+ * time *after* traversals are done, as opposed to the animation frame timing, which is
+ * before any traversals. This timing can be used to adjust the start time of an animation
+ * when expensive traversals create big delta between the animation frame timing and the time
+ * that animation is first shown on screen.
+ *
+ * Note this should only be called when the animation has already registered to receive
+ * animation frame callbacks. This callback will be guaranteed to happen *after* the next
+ * animation frame callback.
+ */
+ public void addOneShotCommitCallback(final AnimationFrameCallback callback) {
+ if (!mCommitCallbacks.contains(callback)) {
+ mCommitCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Removes the given callback from the list, so it will no longer be called for frame related
+ * timing.
+ */
+ public void removeCallback(AnimationFrameCallback callback) {
+ mCommitCallbacks.remove(callback);
+ mDelayedCallbackStartTime.remove(callback);
+ int id = mAnimationCallbacks.indexOf(callback);
+ if (id >= 0) {
+ mAnimationCallbacks.set(id, null);
+ mListDirty = true;
+ }
+ }
+
+ private void doAnimationFrame(long frameTime) {
+ long currentTime = SystemClock.uptimeMillis();
+ final int size = mAnimationCallbacks.size();
+ for (int i = 0; i < size; i++) {
+ final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
+ if (callback == null) {
+ continue;
+ }
+ if (isCallbackDue(callback, currentTime)) {
+ callback.doAnimationFrame(frameTime);
+ if (mCommitCallbacks.contains(callback)) {
+ getProvider().postCommitCallback(new Runnable() {
+ @Override
+ public void run() {
+ commitAnimationFrame(callback, getProvider().getFrameTime());
+ }
+ });
+ }
+ }
+ }
+ cleanUpList();
+ }
+
+ private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
+ if (!mDelayedCallbackStartTime.containsKey(callback) &&
+ mCommitCallbacks.contains(callback)) {
+ callback.commitAnimationFrame(frameTime);
+ mCommitCallbacks.remove(callback);
+ }
+ }
+
+ /**
+ * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay
+ * so that they can start getting frame callbacks.
+ *
+ * @return true if they have passed the initial delay or have no delay, false otherwise.
+ */
+ private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) {
+ Long startTime = mDelayedCallbackStartTime.get(callback);
+ if (startTime == null) {
+ return true;
+ }
+ if (startTime < currentTime) {
+ mDelayedCallbackStartTime.remove(callback);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the number of callbacks that have registered for frame callbacks.
+ */
+ public static int getAnimationCount() {
+ AnimationHandler handler = sAnimatorHandler.get();
+ if (handler == null) {
+ return 0;
+ }
+ return handler.getCallbackSize();
+ }
+
+ public static void setFrameDelay(long delay) {
+ getInstance().getProvider().setFrameDelay(delay);
+ }
+
+ public static long getFrameDelay() {
+ return getInstance().getProvider().getFrameDelay();
+ }
+
+ void autoCancelBasedOn(ObjectAnimator objectAnimator) {
+ for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
+ AnimationFrameCallback cb = mAnimationCallbacks.get(i);
+ if (cb == null) {
+ continue;
+ }
+ if (objectAnimator.shouldAutoCancel(cb)) {
+ ((Animator) mAnimationCallbacks.get(i)).cancel();
+ }
+ }
+ }
+
+ private void cleanUpList() {
+ if (mListDirty) {
+ for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
+ if (mAnimationCallbacks.get(i) == null) {
+ mAnimationCallbacks.remove(i);
+ }
+ }
+ mListDirty = false;
+ }
+ }
+
+ private int getCallbackSize() {
+ int count = 0;
+ int size = mAnimationCallbacks.size();
+ for (int i = size - 1; i >= 0; i--) {
+ if (mAnimationCallbacks.get(i) != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Default provider of timing pulse that uses Choreographer for frame callbacks.
+ */
+ private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
+
+ final Choreographer mChoreographer = Choreographer.getInstance();
+
+ @Override
+ public void postFrameCallback(Choreographer.FrameCallback callback) {
+ mChoreographer.postFrameCallback(callback);
+ }
+
+ @Override
+ public void postCommitCallback(Runnable runnable) {
+ mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
+ }
+
+ @Override
+ public long getFrameTime() {
+ return mChoreographer.getFrameTime();
+ }
+
+ @Override
+ public long getFrameDelay() {
+ return Choreographer.getFrameDelay();
+ }
+
+ @Override
+ public void setFrameDelay(long delay) {
+ Choreographer.setFrameDelay(delay);
+ }
+ }
+
+ /**
+ * Callbacks that receives notifications for animation timing and frame commit timing.
+ * @hide
+ */
+ public interface AnimationFrameCallback {
+ /**
+ * Run animation based on the frame time.
+ * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
+ * base.
+ * @return if the animation has finished.
+ */
+ boolean doAnimationFrame(long frameTime);
+
+ /**
+ * This notifies the callback of frame commit time. Frame commit time is the time after
+ * traversals happen, as opposed to the normal animation frame time that is before
+ * traversals. This is used to compensate expensive traversals that happen as the
+ * animation starts. When traversals take a long time to complete, the rendering of the
+ * initial frame will be delayed (by a long time). But since the startTime of the
+ * animation is set before the traversal, by the time of next frame, a lot of time would
+ * have passed since startTime was set, the animation will consequently skip a few frames
+ * to respect the new frameTime. By having the commit time, we can adjust the start time to
+ * when the first frame was drawn (after any expensive traversals) so that no frames
+ * will be skipped.
+ *
+ * @param frameTime The frame time after traversals happen, if any, in the
+ * {@link SystemClock#uptimeMillis()} time base.
+ */
+ void commitAnimationFrame(long frameTime);
+ }
+
+ /**
+ * The intention for having this interface is to increase the testability of ValueAnimator.
+ * Specifically, we can have a custom implementation of the interface below and provide
+ * timing pulse without using Choreographer. That way we could use any arbitrary interval for
+ * our timing pulse in the tests.
+ *
+ * @hide
+ */
+ public interface AnimationFrameCallbackProvider {
+ void postFrameCallback(Choreographer.FrameCallback callback);
+ void postCommitCallback(Runnable runnable);
+ long getFrameTime();
+ long getFrameDelay();
+ void setFrameDelay(long delay);
+ }
+}
diff --git a/android-34/android/animation/Animator.java b/android-34/android/animation/Animator.java
new file mode 100644
index 0000000..12026aa
--- /dev/null
+++ b/android-34/android/animation/Animator.java
@@ -0,0 +1,860 @@
+/*
+ * 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.animation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConstantState;
+import android.os.Build;
+import android.util.LongArray;
+
+import java.util.ArrayList;
+
+/**
+ * This is the superclass for classes which provide basic support for animations which can be
+ * started, ended, and have <code>AnimatorListeners</code> added to them.
+ */
+public abstract class Animator implements Cloneable {
+
+ /**
+ * The value used to indicate infinite duration (e.g. when Animators repeat infinitely).
+ */
+ public static final long DURATION_INFINITE = -1;
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ ArrayList<AnimatorListener> mListeners = null;
+
+ /**
+ * The set of listeners to be sent pause/resume events through the life
+ * of an animation.
+ */
+ ArrayList<AnimatorPauseListener> mPauseListeners = null;
+
+ /**
+ * Whether this animator is currently in a paused state.
+ */
+ boolean mPaused = false;
+
+ /**
+ * A set of flags which identify the type of configuration changes that can affect this
+ * Animator. Used by the Animator cache.
+ */
+ @Config int mChangingConfigurations = 0;
+
+ /**
+ * If this animator is inflated from a constant state, keep a reference to it so that
+ * ConstantState will not be garbage collected until this animator is collected
+ */
+ private AnimatorConstantState mConstantState;
+
+ /**
+ * backing field for backgroundPauseDelay property. This could be simply a hardcoded
+ * value in AnimationHandler, but it is useful to be able to change the value in tests.
+ */
+ private static long sBackgroundPauseDelay = 1000;
+
+ /**
+ * A cache of the values in a list. Used so that when calling the list, we have a copy
+ * of it in case the list is modified while iterating. The array can be reused to avoid
+ * allocation on every notification.
+ */
+ private Object[] mCachedList;
+
+ /**
+ * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
+ * complex to keep track of since we notify listeners at different times depending on
+ * startDelay and whether start() was called before end().
+ */
+ boolean mStartListenersCalled = false;
+
+ /**
+ * Sets the duration for delaying pausing animators when apps go into the background.
+ * Used by AnimationHandler when requested to pause animators.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setBackgroundPauseDelay(long value) {
+ sBackgroundPauseDelay = value;
+ }
+
+ /**
+ * Gets the duration for delaying pausing animators when apps go into the background.
+ * Used by AnimationHandler when requested to pause animators.
+ *
+ * @hide
+ */
+ @TestApi
+ public static long getBackgroundPauseDelay() {
+ return sBackgroundPauseDelay;
+ }
+
+ /**
+ * Sets the behavior of animator pausing when apps go into the background.
+ * This is exposed as a test API for verification, but is intended for use by internal/
+ * platform code, potentially for use by a system property that could disable it
+ * system wide.
+ *
+ * @param enable Enable (default behavior) or disable background pausing behavior.
+ * @hide
+ */
+ @TestApi
+ public static void setAnimatorPausingEnabled(boolean enable) {
+ AnimationHandler.setAnimatorPausingEnabled(enable);
+ AnimationHandler.setOverrideAnimatorPausingSystemProperty(!enable);
+ }
+
+ /**
+ * Starts this animation. If the animation has a nonzero startDelay, the animation will start
+ * running after that delay elapses. A non-delayed animation will have its initial
+ * value(s) set immediately, followed by calls to
+ * {@link AnimatorListener#onAnimationStart(Animator)} for any listeners of this animator.
+ *
+ * <p>The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.</p>
+ *
+ */
+ public void start() {
+ }
+
+ /**
+ * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
+ * stop in its tracks, sending an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
+ * its listeners, followed by an
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
+ */
+ public void cancel() {
+ }
+
+ /**
+ * Ends the animation. This causes the animation to assign the end value of the property being
+ * animated, then calling the
+ * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
+ * its listeners.
+ *
+ * <p>This method must be called on the thread that is running the animation.</p>
+ */
+ public void end() {
+ }
+
+ /**
+ * Pauses a running animation. This method should only be called on the same thread on
+ * which the animation was started. If the animation has not yet been {@link
+ * #isStarted() started} or has since ended, then the call is ignored. Paused
+ * animations can be resumed by calling {@link #resume()}.
+ *
+ * @see #resume()
+ * @see #isPaused()
+ * @see AnimatorPauseListener
+ */
+ public void pause() {
+ // We only want to pause started Animators or animators that setCurrentPlayTime()
+ // have been called on. mStartListenerCalled will be true if seek has happened.
+ if ((isStarted() || mStartListenersCalled) && !mPaused) {
+ mPaused = true;
+ notifyPauseListeners(AnimatorCaller.ON_PAUSE);
+ }
+ }
+
+ /**
+ * Resumes a paused animation, causing the animator to pick up where it left off
+ * when it was paused. This method should only be called on the same thread on
+ * which the animation was started. Calls to resume() on an animator that is
+ * not currently paused will be ignored.
+ *
+ * @see #pause()
+ * @see #isPaused()
+ * @see AnimatorPauseListener
+ */
+ public void resume() {
+ if (mPaused) {
+ mPaused = false;
+ notifyPauseListeners(AnimatorCaller.ON_RESUME);
+ }
+ }
+
+ /**
+ * Returns whether this animator is currently in a paused state.
+ *
+ * @return True if the animator is currently paused, false otherwise.
+ *
+ * @see #pause()
+ * @see #resume()
+ */
+ public boolean isPaused() {
+ return mPaused;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay processing the animation
+ * after {@link #start()} is called.
+ *
+ * @return the number of milliseconds to delay running the animation
+ */
+ public abstract long getStartDelay();
+
+ /**
+ * The amount of time, in milliseconds, to delay processing the animation
+ * after {@link #start()} is called.
+
+ * @param startDelay The amount of the delay, in milliseconds
+ */
+ public abstract void setStartDelay(long startDelay);
+
+ /**
+ * Sets the duration of the animation.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ */
+ public abstract Animator setDuration(long duration);
+
+ /**
+ * Gets the duration of the animation.
+ *
+ * @return The length of the animation, in milliseconds.
+ */
+ public abstract long getDuration();
+
+ /**
+ * Gets the total duration of the animation, accounting for animation sequences, start delay,
+ * and repeating. Return {@link #DURATION_INFINITE} if the duration is infinite.
+ *
+ * @return Total time an animation takes to finish, starting from the time {@link #start()}
+ * is called. {@link #DURATION_INFINITE} will be returned if the animation or any
+ * child animation repeats infinite times.
+ */
+ public long getTotalDuration() {
+ long duration = getDuration();
+ if (duration == DURATION_INFINITE) {
+ return DURATION_INFINITE;
+ } else {
+ return getStartDelay() + duration;
+ }
+ }
+
+ /**
+ * The time interpolator used in calculating the elapsed fraction of the
+ * animation. The interpolator determines whether the animation runs with
+ * linear or non-linear motion, such as acceleration and deceleration. The
+ * default value is {@link android.view.animation.AccelerateDecelerateInterpolator}.
+ *
+ * @param value the interpolator to be used by this animation
+ */
+ public abstract void setInterpolator(TimeInterpolator value);
+
+ /**
+ * Returns the timing interpolator that this animation uses.
+ *
+ * @return The timing interpolator for this animation.
+ */
+ public TimeInterpolator getInterpolator() {
+ return null;
+ }
+
+ /**
+ * Returns whether this Animator is currently running (having been started and gone past any
+ * initial startDelay period and not yet ended).
+ *
+ * @return Whether the Animator is running.
+ */
+ public abstract boolean isRunning();
+
+ /**
+ * Returns whether this Animator has been started and not yet ended. For reusable
+ * Animators (which most Animators are, apart from the one-shot animator produced by
+ * {@link android.view.ViewAnimationUtils#createCircularReveal(
+ * android.view.View, int, int, float, float) createCircularReveal()}),
+ * this state is a superset of {@link #isRunning()}, because an Animator with a
+ * nonzero {@link #getStartDelay() startDelay} will return true for {@link #isStarted()} during
+ * the delay phase, whereas {@link #isRunning()} will return true only after the delay phase
+ * is complete. Non-reusable animators will always return true after they have been
+ * started, because they cannot return to a non-started state.
+ *
+ * @return Whether the Animator has been started and not yet ended.
+ */
+ public boolean isStarted() {
+ // Default method returns value for isRunning(). Subclasses should override to return a
+ // real value.
+ return isRunning();
+ }
+
+ /**
+ * Adds a listener to the set of listeners that are sent events through the life of an
+ * animation, such as start, repeat, and end.
+ *
+ * @param listener the listener to be added to the current set of listeners for this animation.
+ */
+ public void addListener(AnimatorListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<AnimatorListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener from the set listening to this animation.
+ *
+ * @param listener the listener to be removed from the current set of listeners for this
+ * animation.
+ */
+ public void removeListener(AnimatorListener listener) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ mListeners = null;
+ }
+ }
+
+ /**
+ * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently
+ * listening for events on this <code>Animator</code> object.
+ *
+ * @return ArrayList<AnimatorListener> The set of listeners.
+ */
+ public ArrayList<AnimatorListener> getListeners() {
+ return mListeners;
+ }
+
+ /**
+ * Adds a pause listener to this animator.
+ *
+ * @param listener the listener to be added to the current set of pause listeners
+ * for this animation.
+ */
+ public void addPauseListener(AnimatorPauseListener listener) {
+ if (mPauseListeners == null) {
+ mPauseListeners = new ArrayList<AnimatorPauseListener>();
+ }
+ mPauseListeners.add(listener);
+ }
+
+ /**
+ * Removes a pause listener from the set listening to this animation.
+ *
+ * @param listener the listener to be removed from the current set of pause
+ * listeners for this animation.
+ */
+ public void removePauseListener(AnimatorPauseListener listener) {
+ if (mPauseListeners == null) {
+ return;
+ }
+ mPauseListeners.remove(listener);
+ if (mPauseListeners.size() == 0) {
+ mPauseListeners = null;
+ }
+ }
+
+ /**
+ * Removes all {@link #addListener(android.animation.Animator.AnimatorListener) listeners}
+ * and {@link #addPauseListener(android.animation.Animator.AnimatorPauseListener)
+ * pauseListeners} from this object.
+ */
+ public void removeAllListeners() {
+ if (mListeners != null) {
+ mListeners.clear();
+ mListeners = null;
+ }
+ if (mPauseListeners != null) {
+ mPauseListeners.clear();
+ mPauseListeners = null;
+ }
+ }
+
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be re-created from Resources. The default implementation returns whatever
+ * value was provided through setChangingConfigurations(int) or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public @Config int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created from resource.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(@Config int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(@Config int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed, instead
+ * of re-loading it from resources. Default implementation creates a new
+ * {@link AnimatorConstantState}. You can override this method to provide your custom logic or
+ * return null if you don't want this animator to be cached.
+ *
+ * @return The ConfigurationBoundResourceCache.BaseConstantState associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<Animator> createConstantState() {
+ return new AnimatorConstantState(this);
+ }
+
+ @Override
+ public Animator clone() {
+ try {
+ final Animator anim = (Animator) super.clone();
+ if (mListeners != null) {
+ anim.mListeners = new ArrayList<AnimatorListener>(mListeners);
+ }
+ if (mPauseListeners != null) {
+ anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
+ }
+ anim.mCachedList = null;
+ anim.mStartListenersCalled = false;
+ return anim;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * This method tells the object to use appropriate information to extract
+ * starting values for the animation. For example, a AnimatorSet object will pass
+ * this call to its child objects to tell them to set up the values. A
+ * ObjectAnimator object will use the information it has about its target object
+ * and PropertyValuesHolder objects to get the start values for its properties.
+ * A ValueAnimator object will ignore the request since it does not have enough
+ * information (such as a target object) to gather these values.
+ */
+ public void setupStartValues() {
+ }
+
+ /**
+ * This method tells the object to use appropriate information to extract
+ * ending values for the animation. For example, a AnimatorSet object will pass
+ * this call to its child objects to tell them to set up the values. A
+ * ObjectAnimator object will use the information it has about its target object
+ * and PropertyValuesHolder objects to get the start values for its properties.
+ * A ValueAnimator object will ignore the request since it does not have enough
+ * information (such as a target object) to gather these values.
+ */
+ public void setupEndValues() {
+ }
+
+ /**
+ * Sets the target object whose property will be animated by this animation. Not all subclasses
+ * operate on target objects (for example, {@link ValueAnimator}, but this method
+ * is on the superclass for the convenience of dealing generically with those subclasses
+ * that do handle targets.
+ * <p>
+ * <strong>Note:</strong> The target is stored as a weak reference internally to avoid leaking
+ * resources by having animators directly reference old targets. Therefore, you should
+ * ensure that animator targets always have a hard reference elsewhere.
+ *
+ * @param target The object being animated
+ */
+ public void setTarget(@Nullable Object target) {
+ }
+
+ // Hide reverse() and canReverse() for now since reverse() only work for simple
+ // cases, like we don't support sequential, neither startDelay.
+ // TODO: make reverse() works for all the Animators.
+ /**
+ * @hide
+ */
+ public boolean canReverse() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void reverse() {
+ throw new IllegalStateException("Reverse is not supported");
+ }
+
+ // Pulse an animation frame into the animation.
+ boolean pulseAnimationFrame(long frameTime) {
+ // TODO: Need to find a better signal than this. There's a bug in SystemUI that's preventing
+ // returning !isStarted() from working.
+ return false;
+ }
+
+ /**
+ * Internal use only.
+ * This call starts the animation in regular or reverse direction without requiring them to
+ * register frame callbacks. The caller will be responsible for all the subsequent animation
+ * pulses. Specifically, the caller needs to call doAnimationFrame(...) for the animation on
+ * every frame.
+ *
+ * @param inReverse whether the animation should play in reverse direction
+ */
+ void startWithoutPulsing(boolean inReverse) {
+ if (inReverse) {
+ reverse();
+ } else {
+ start();
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Skips the animation value to end/start, depending on whether the play direction is forward
+ * or backward.
+ *
+ * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+ * equivalent to skip to start value in a forward playing direction.
+ */
+ void skipToEndValue(boolean inReverse) {}
+
+ /**
+ * Internal use only.
+ *
+ * Returns whether the animation has start/end values setup. For most of the animations, this
+ * should always be true. For ObjectAnimators, the start values are setup in the initialization
+ * of the animation.
+ */
+ boolean isInitialized() {
+ return true;
+ }
+
+ /**
+ * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
+ * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
+ * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
+ * done between the two times.
+ */
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+ /**
+ * Internal use only. This animates any animation that has ended since lastPlayTime.
+ * If an animation hasn't been finished, no change will be made.
+ */
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+ /**
+ * Internal use only. Adds all start times (after delay) to and end times to times.
+ * The value must include offset.
+ */
+ void getStartAndEndTimes(LongArray times, long offset) {
+ long startTime = offset + getStartDelay();
+ if (times.indexOf(startTime) < 0) {
+ times.add(startTime);
+ }
+ long duration = getTotalDuration();
+ if (duration != DURATION_INFINITE) {
+ long endTime = duration + offset;
+ if (times.indexOf(endTime) < 0) {
+ times.add(endTime);
+ }
+ }
+ }
+
+ /**
+ * Calls notification for each AnimatorListener.
+ *
+ * @param notification The notification method to call on each listener.
+ * @param isReverse When this is used with start/end, this is the isReverse parameter. For
+ * other calls, this is ignored.
+ */
+ void notifyListeners(
+ AnimatorCaller<AnimatorListener, Animator> notification,
+ boolean isReverse
+ ) {
+ callOnList(mListeners, notification, this, isReverse);
+ }
+
+ /**
+ * Call pause/resume on each AnimatorPauseListener.
+ *
+ * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each
+ * listener.
+ */
+ void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) {
+ callOnList(mPauseListeners, notification, this, false);
+ }
+
+ void notifyStartListeners(boolean isReversing) {
+ boolean startListenersCalled = mStartListenersCalled;
+ mStartListenersCalled = true;
+ if (mListeners != null && !startListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_START, isReversing);
+ }
+ }
+
+ void notifyEndListeners(boolean isReversing) {
+ boolean startListenersCalled = mStartListenersCalled;
+ mStartListenersCalled = false;
+ if (mListeners != null && startListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_END, isReversing);
+ }
+ }
+
+ /**
+ * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and
+ * <code>isReverse</code> as parameters.
+ *
+ * @param list The list of items to make calls on.
+ * @param call The method to call for each item in list.
+ * @param animator The animator parameter of call.
+ * @param isReverse The isReverse parameter of call.
+ * @param <T> The item type of list
+ * @param <A> The Animator type of animator.
+ */
+ <T, A> void callOnList(
+ ArrayList<T> list,
+ AnimatorCaller<T, A> call,
+ A animator,
+ boolean isReverse
+ ) {
+ int size = list == null ? 0 : list.size();
+ if (size > 0) {
+ // Try to reuse mCacheList to store the items of list.
+ Object[] array;
+ if (mCachedList == null || mCachedList.length < size) {
+ array = new Object[size];
+ } else {
+ array = mCachedList;
+ // Clear it in case there is some reentrancy
+ mCachedList = null;
+ }
+ list.toArray(array);
+ for (int i = 0; i < size; i++) {
+ //noinspection unchecked
+ T item = (T) array[i];
+ call.call(item, animator, isReverse);
+ array[i] = null;
+ }
+ // Store it for the next call so we can reuse this array, if needed.
+ mCachedList = array;
+ }
+ }
+
+ /**
+ * <p>An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * repetition of the animation.</p>
+ */
+ public static interface AnimatorListener {
+
+ /**
+ * <p>Notifies the start of the animation as well as the animation's overall play direction.
+ * This method's default behavior is to call {@link #onAnimationStart(Animator)}. This
+ * method can be overridden, though not required, to get the additional play direction info
+ * when an animation starts. Skipping calling super when overriding this method results in
+ * {@link #onAnimationStart(Animator)} not getting called.
+ *
+ * @param animation The started animation.
+ * @param isReverse Whether the animation is playing in reverse.
+ */
+ default void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+ onAnimationStart(animation);
+ }
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * <p>This method's default behavior is to call {@link #onAnimationEnd(Animator)}. This
+ * method can be overridden, though not required, to get the additional play direction info
+ * when an animation ends. Skipping calling super when overriding this method results in
+ * {@link #onAnimationEnd(Animator)} not getting called.
+ *
+ * @param animation The animation which reached its end.
+ * @param isReverse Whether the animation is playing in reverse.
+ */
+ default void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ onAnimationEnd(animation);
+ }
+
+ /**
+ * <p>Notifies the start of the animation.</p>
+ *
+ * @param animation The started animation.
+ */
+ void onAnimationStart(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies the end of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which reached its end.
+ */
+ void onAnimationEnd(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies the cancellation of the animation. This callback is not invoked
+ * for animations with repeat count set to INFINITE.</p>
+ *
+ * @param animation The animation which was canceled.
+ */
+ void onAnimationCancel(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies the repetition of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationRepeat(@NonNull Animator animation);
+ }
+
+ /**
+ * A pause listener receives notifications from an animation when the
+ * animation is {@link #pause() paused} or {@link #resume() resumed}.
+ *
+ * @see #addPauseListener(AnimatorPauseListener)
+ */
+ public static interface AnimatorPauseListener {
+ /**
+ * <p>Notifies that the animation was paused.</p>
+ *
+ * @param animation The animaton being paused.
+ * @see #pause()
+ */
+ void onAnimationPause(@NonNull Animator animation);
+
+ /**
+ * <p>Notifies that the animation was resumed, after being
+ * previously paused.</p>
+ *
+ * @param animation The animation being resumed.
+ * @see #resume()
+ */
+ void onAnimationResume(@NonNull Animator animation);
+ }
+
+ /**
+ * <p>Whether or not the Animator is allowed to run asynchronously off of
+ * the UI thread. This is a hint that informs the Animator that it is
+ * OK to run the animation off-thread, however the Animator may decide
+ * that it must run the animation on the UI thread anyway.
+ *
+ * <p>Regardless of whether or not the animation runs asynchronously, all
+ * listener callbacks will be called on the UI thread.</p>
+ *
+ * <p>To be able to use this hint the following must be true:</p>
+ * <ol>
+ * <li>The animator is immutable while {@link #isStarted()} is true. Requests
+ * to change duration, delay, etc... may be ignored.</li>
+ * <li>Lifecycle callback events may be asynchronous. Events such as
+ * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
+ * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
+ * as they must be posted back to the UI thread, and any actions performed
+ * by those callbacks (such as starting new animations) will not happen
+ * in the same frame.</li>
+ * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
+ * may be asynchronous. It is guaranteed that all state changes that are
+ * performed on the UI thread in the same frame will be applied as a single
+ * atomic update, however that frame may be the current frame,
+ * the next frame, or some future frame. This will also impact the observed
+ * state of the Animator. For example, {@link #isStarted()} may still return true
+ * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
+ * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
+ * for this reason.</li>
+ * </ol>
+ * @hide
+ */
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ // It is up to subclasses to support this, if they can.
+ }
+
+ /**
+ * Creates a {@link ConstantState} which holds changing configurations information associated
+ * with the given Animator.
+ * <p>
+ * When {@link #newInstance()} is called, default implementation clones the Animator.
+ */
+ private static class AnimatorConstantState extends ConstantState<Animator> {
+
+ final Animator mAnimator;
+ @Config int mChangingConf;
+
+ public AnimatorConstantState(Animator animator) {
+ mAnimator = animator;
+ // ensure a reference back to here so that constante state is not gc'ed.
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public Animator newInstance() {
+ final Animator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
+
+ /**
+ * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to
+ * make a call on all children of a list. This can be for start, stop, pause, cancel, update,
+ * etc notifications.
+ *
+ * @param <T> The type of listener to make the call on
+ * @param <A> The type of animator that is passed as a parameter
+ */
+ interface AnimatorCaller<T, A> {
+ void call(T listener, A animator, boolean isReverse);
+
+ AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart;
+ AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd;
+ AnimatorCaller<AnimatorListener, Animator> ON_CANCEL =
+ (listener, animator, isReverse) -> listener.onAnimationCancel(animator);
+ AnimatorCaller<AnimatorListener, Animator> ON_REPEAT =
+ (listener, animator, isReverse) -> listener.onAnimationRepeat(animator);
+ AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE =
+ (listener, animator, isReverse) -> listener.onAnimationPause(animator);
+ AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME =
+ (listener, animator, isReverse) -> listener.onAnimationResume(animator);
+ AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE =
+ (listener, animator, isReverse) -> listener.onAnimationUpdate(animator);
+ }
+}
diff --git a/android-34/android/animation/AnimatorInflater.java b/android-34/android/animation/AnimatorInflater.java
new file mode 100644
index 0000000..f69bbfd
--- /dev/null
+++ b/android-34/android/animation/AnimatorInflater.java
@@ -0,0 +1,1082 @@
+/*
+ * 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.animation;
+
+import android.annotation.AnimatorRes;
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConfigurationBoundResourceCache;
+import android.content.res.ConstantState;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.PathParser;
+import android.util.StateSet;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InflateException;
+import android.view.animation.AnimationUtils;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used to instantiate animator XML files into Animator objects.
+ * <p>
+ * For performance reasons, inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use this inflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class AnimatorInflater {
+ private static final String TAG = "AnimatorInflater";
+ /**
+ * These flags are used when parsing AnimatorSet objects
+ */
+ private static final int TOGETHER = 0;
+ private static final int SEQUENTIALLY = 1;
+
+ /**
+ * Enum values used in XML attributes to indicate the value for mValueType
+ */
+ private static final int VALUE_TYPE_FLOAT = 0;
+ private static final int VALUE_TYPE_INT = 1;
+ private static final int VALUE_TYPE_PATH = 2;
+ private static final int VALUE_TYPE_COLOR = 3;
+ private static final int VALUE_TYPE_UNDEFINED = 4;
+
+ private static final boolean DBG_ANIMATOR_INFLATER = false;
+
+ // used to calculate changing configs for resource references
+ private static final TypedValue sTmpTypedValue = new TypedValue();
+
+ /**
+ * Loads an {@link Animator} object from a resource
+ *
+ * @param context Application context used to access resources
+ * @param id The resource id of the animation to load
+ * @return The animator object reference by the specified id
+ * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+ */
+ public static Animator loadAnimator(Context context, @AnimatorRes int id)
+ throws NotFoundException {
+ return loadAnimator(context.getResources(), context.getTheme(), id);
+ }
+
+ /**
+ * Loads an {@link Animator} object from a resource
+ *
+ * @param resources The resources
+ * @param theme The theme
+ * @param id The resource id of the animation to load
+ * @return The animator object reference by the specified id
+ * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+ * @hide
+ */
+ public static Animator loadAnimator(Resources resources, Theme theme, int id)
+ throws NotFoundException {
+ return loadAnimator(resources, theme, id, 1);
+ }
+
+ /** @hide */
+ public static Animator loadAnimator(Resources resources, Theme theme, int id,
+ float pathErrorScale) throws NotFoundException {
+ final ConfigurationBoundResourceCache<Animator> animatorCache = resources
+ .getAnimatorCache();
+ Animator animator = animatorCache.getInstance(id, resources, theme);
+ if (animator != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
+ }
+ return animator;
+ } else if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
+ }
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getAnimation(id);
+ animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<Animator> constantState = animator.createConstantState();
+ if (constantState != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
+ }
+ animatorCache.put(id, theme, constantState);
+ // create a new animator so that cached version is never used by the user
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
+ } catch (XmlPullParserException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException("Can't load animation resource ID #0x" +
+ Integer.toHexString(id));
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ public static StateListAnimator loadStateListAnimator(Context context, int id)
+ throws NotFoundException {
+ final Resources resources = context.getResources();
+ final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
+ .getStateListAnimatorCache();
+ final Theme theme = context.getTheme();
+ StateListAnimator animator = cache.getInstance(id, resources, theme);
+ if (animator != null) {
+ return animator;
+ }
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getAnimation(id);
+ animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<StateListAnimator> constantState = animator
+ .createConstantState();
+ if (constantState != null) {
+ cache.put(id, theme, constantState);
+ // return a clone so that the animator in constant state is never used.
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
+ } catch (XmlPullParserException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException(
+ "Can't load state list animator resource ID #0x" +
+ Integer.toHexString(id)
+ );
+ rnf.initCause(ex);
+ throw rnf;
+ } catch (IOException ex) {
+ Resources.NotFoundException rnf =
+ new Resources.NotFoundException(
+ "Can't load state list animator resource ID #0x" +
+ Integer.toHexString(id)
+ );
+ rnf.initCause(ex);
+ throw rnf;
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ private static StateListAnimator createStateListAnimatorFromXml(Context context,
+ XmlPullParser parser, AttributeSet attributeSet)
+ throws IOException, XmlPullParserException {
+ int type;
+ StateListAnimator stateListAnimator = new StateListAnimator();
+
+ while (true) {
+ type = parser.next();
+ switch (type) {
+ case XmlPullParser.END_DOCUMENT:
+ case XmlPullParser.END_TAG:
+ return stateListAnimator;
+
+ case XmlPullParser.START_TAG:
+ // parse item
+ Animator animator = null;
+ if ("item".equals(parser.getName())) {
+ int attributeCount = parser.getAttributeCount();
+ int[] states = new int[attributeCount];
+ int stateIndex = 0;
+ for (int i = 0; i < attributeCount; i++) {
+ int attrName = attributeSet.getAttributeNameResource(i);
+ if (attrName == R.attr.animation) {
+ final int animId = attributeSet.getAttributeResourceValue(i, 0);
+ animator = loadAnimator(context, animId);
+ } else {
+ states[stateIndex++] =
+ attributeSet.getAttributeBooleanValue(i, false) ?
+ attrName : -attrName;
+ }
+ }
+ if (animator == null) {
+ animator = createAnimatorFromXml(context.getResources(),
+ context.getTheme(), parser, 1f);
+ }
+
+ if (animator == null) {
+ throw new Resources.NotFoundException(
+ "animation state item must have a valid animation");
+ }
+ stateListAnimator
+ .addState(StateSet.trimStateSet(states, stateIndex), animator);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * PathDataEvaluator is used to interpolate between two paths which are
+ * represented in the same format but different control points' values.
+ * The path is represented as verbs and points for each of the verbs.
+ */
+ private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
+ private final PathParser.PathData mPathData = new PathParser.PathData();
+
+ @Override
+ public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
+ PathParser.PathData endPathData) {
+ if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
+ throw new IllegalArgumentException("Can't interpolate between"
+ + " two incompatible pathData");
+ }
+ return mPathData;
+ }
+ }
+
+ private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
+ int valueFromId, int valueToId, String propertyName) {
+
+ TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
+ boolean hasFrom = (tvFrom != null);
+ int fromType = hasFrom ? tvFrom.type : 0;
+ TypedValue tvTo = styledAttributes.peekValue(valueToId);
+ boolean hasTo = (tvTo != null);
+ int toType = hasTo ? tvTo.type : 0;
+
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // Check whether it's color type. If not, fall back to default type (i.e. float type)
+ if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
+
+ boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
+ PropertyValuesHolder returnValue = null;
+
+ if (valueType == VALUE_TYPE_PATH) {
+ String fromString = styledAttributes.getString(valueFromId);
+ String toString = styledAttributes.getString(valueToId);
+ PathParser.PathData nodesFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData nodesTo = toString == null
+ ? null : new PathParser.PathData(toString);
+
+ if (nodesFrom != null || nodesTo != null) {
+ if (nodesFrom != null) {
+ TypeEvaluator evaluator = new PathDataEvaluator();
+ if (nodesTo != null) {
+ if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ throw new InflateException(" Can't morph from " + fromString + " to " +
+ toString);
+ }
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ nodesFrom, nodesTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesFrom);
+ }
+ } else if (nodesTo != null) {
+ TypeEvaluator evaluator = new PathDataEvaluator();
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesTo);
+ }
+ }
+ } else {
+ TypeEvaluator evaluator = null;
+ // Integer and float value types are handled here.
+ if (valueType == VALUE_TYPE_COLOR) {
+ // special case for colors: ignore valueType and get ints
+ evaluator = ArgbEvaluator.getInstance();
+ }
+ if (getFloats) {
+ float valueFrom;
+ float valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = styledAttributes.getDimension(valueFromId, 0f);
+ } else {
+ valueFrom = styledAttributes.getFloat(valueFromId, 0f);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName,
+ valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom);
+ }
+ } else {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo);
+ }
+ } else {
+ int valueFrom;
+ int valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
+ } else if (isColorType(fromType)) {
+ valueFrom = styledAttributes.getColor(valueFromId, 0);
+ } else {
+ valueFrom = styledAttributes.getInt(valueFromId, 0);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom);
+ }
+ } else {
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo);
+ }
+ }
+ }
+ if (returnValue != null && evaluator != null) {
+ returnValue.setEvaluator(evaluator);
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * @param anim The animator, must not be null
+ * @param arrayAnimator Incoming typed array for Animator's attributes.
+ * @param arrayObjectAnimator Incoming typed array for Object Animator's
+ * attributes.
+ * @param pixelSize The relative pixel size, used to calculate the
+ * maximum error for path animations.
+ */
+ private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+ TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
+ long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
+
+ long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
+
+ int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_UNDEFINED);
+
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ valueType = inferValueTypeFromValues(arrayAnimator, R.styleable.Animator_valueFrom,
+ R.styleable.Animator_valueTo);
+ }
+ PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType,
+ R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, "");
+ if (pvh != null) {
+ anim.setValues(pvh);
+ }
+
+ anim.setDuration(duration);
+ anim.setStartDelay(startDelay);
+
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatCount)) {
+ anim.setRepeatCount(
+ arrayAnimator.getInt(R.styleable.Animator_repeatCount, 0));
+ }
+ if (arrayAnimator.hasValue(R.styleable.Animator_repeatMode)) {
+ anim.setRepeatMode(
+ arrayAnimator.getInt(R.styleable.Animator_repeatMode,
+ ValueAnimator.RESTART));
+ }
+
+ if (arrayObjectAnimator != null) {
+ setupObjectAnimator(anim, arrayObjectAnimator, valueType, pixelSize);
+ }
+ }
+
+ /**
+ * Setup the Animator to achieve path morphing.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayAnimator TypedArray for the ValueAnimator.
+ * @return the PathDataEvaluator.
+ */
+ private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim,
+ TypedArray arrayAnimator) {
+ TypeEvaluator evaluator = null;
+ String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
+ String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
+ PathParser.PathData pathDataFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData pathDataTo = toString == null
+ ? null : new PathParser.PathData(toString);
+
+ if (pathDataFrom != null) {
+ if (pathDataTo != null) {
+ anim.setObjectValues(pathDataFrom, pathDataTo);
+ if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
+ throw new InflateException(arrayAnimator.getPositionDescription()
+ + " Can't morph from " + fromString + " to " + toString);
+ }
+ } else {
+ anim.setObjectValues((Object)pathDataFrom);
+ }
+ evaluator = new PathDataEvaluator();
+ } else if (pathDataTo != null) {
+ anim.setObjectValues((Object)pathDataTo);
+ evaluator = new PathDataEvaluator();
+ }
+
+ if (DBG_ANIMATOR_INFLATER && evaluator != null) {
+ Log.v(TAG, "create a new PathDataEvaluator here");
+ }
+
+ return evaluator;
+ }
+
+ /**
+ * Setup ObjectAnimator's property or values from pathData.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayObjectAnimator TypedArray for the ObjectAnimator.
+ * @param getFloats True if the value type is float.
+ * @param pixelSize The relative pixel size, used to calculate the
+ * maximum error for path animations.
+ */
+ private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator,
+ int valueType, float pixelSize) {
+ ObjectAnimator oa = (ObjectAnimator) anim;
+ String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData);
+
+ // Path can be involved in an ObjectAnimator in the following 3 ways:
+ // 1) Path morphing: the property to be animated is pathData, and valueFrom and valueTo
+ // are both of pathType. valueType = pathType needs to be explicitly defined.
+ // 2) A property in X or Y dimension can be animated along a path: the property needs to be
+ // defined in propertyXName or propertyYName attribute, the path will be defined in the
+ // pathData attribute. valueFrom and valueTo will not be necessary for this animation.
+ // 3) PathInterpolator can also define a path (in pathData) for its interpolation curve.
+ // Here we are dealing with case 2:
+ if (pathData != null) {
+ String propertyXName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName);
+ String propertyYName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyYName);
+
+ if (valueType == VALUE_TYPE_PATH || valueType == VALUE_TYPE_UNDEFINED) {
+ // When pathData is defined, we are in case #2 mentioned above. ValueType can only
+ // be float type, or int type. Otherwise we fallback to default type.
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ if (propertyXName == null && propertyYName == null) {
+ throw new InflateException(arrayObjectAnimator.getPositionDescription()
+ + " propertyXName or propertyYName is needed for PathData");
+ } else {
+ Path path = PathParser.createPathFromPathData(pathData);
+ float error = 0.5f * pixelSize; // max half a pixel error
+ PathKeyframes keyframeSet = KeyframeSet.ofPath(path, error);
+ Keyframes xKeyframes;
+ Keyframes yKeyframes;
+ if (valueType == VALUE_TYPE_FLOAT) {
+ xKeyframes = keyframeSet.createXFloatKeyframes();
+ yKeyframes = keyframeSet.createYFloatKeyframes();
+ } else {
+ xKeyframes = keyframeSet.createXIntKeyframes();
+ yKeyframes = keyframeSet.createYIntKeyframes();
+ }
+ PropertyValuesHolder x = null;
+ PropertyValuesHolder y = null;
+ if (propertyXName != null) {
+ x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes);
+ }
+ if (propertyYName != null) {
+ y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes);
+ }
+ if (x == null) {
+ oa.setValues(y);
+ } else if (y == null) {
+ oa.setValues(x);
+ } else {
+ oa.setValues(x, y);
+ }
+ }
+ } else {
+ String propertyName =
+ arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyName);
+ oa.setPropertyName(propertyName);
+ }
+ }
+
+ /**
+ * Setup ValueAnimator's values.
+ * This will handle all of the integer, float and color types.
+ *
+ * @param anim The target Animator which will be updated.
+ * @param arrayAnimator TypedArray for the ValueAnimator.
+ * @param getFloats True if the value type is float.
+ * @param hasFrom True if "valueFrom" exists.
+ * @param fromType The type of "valueFrom".
+ * @param hasTo True if "valueTo" exists.
+ * @param toType The type of "valueTo".
+ */
+ private static void setupValues(ValueAnimator anim, TypedArray arrayAnimator,
+ boolean getFloats, boolean hasFrom, int fromType, boolean hasTo, int toType) {
+ int valueFromIndex = R.styleable.Animator_valueFrom;
+ int valueToIndex = R.styleable.Animator_valueTo;
+ if (getFloats) {
+ float valueFrom;
+ float valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = arrayAnimator.getDimension(valueFromIndex, 0f);
+ } else {
+ valueFrom = arrayAnimator.getFloat(valueFromIndex, 0f);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
+ } else {
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
+ }
+ anim.setFloatValues(valueFrom, valueTo);
+ } else {
+ anim.setFloatValues(valueFrom);
+ }
+ } else {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = arrayAnimator.getDimension(valueToIndex, 0f);
+ } else {
+ valueTo = arrayAnimator.getFloat(valueToIndex, 0f);
+ }
+ anim.setFloatValues(valueTo);
+ }
+ } else {
+ int valueFrom;
+ int valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
+ } else if (isColorType(fromType)) {
+ valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
+ } else {
+ valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
+ } else {
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
+ }
+ anim.setIntValues(valueFrom, valueTo);
+ } else {
+ anim.setIntValues(valueFrom);
+ }
+ } else {
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
+ } else if (isColorType(toType)) {
+ valueTo = arrayAnimator.getColor(valueToIndex, 0);
+ } else {
+ valueTo = arrayAnimator.getInt(valueToIndex, 0);
+ }
+ anim.setIntValues(valueTo);
+ }
+ }
+ }
+ }
+
+ private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
+ float pixelSize)
+ throws XmlPullParserException, IOException {
+ return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0,
+ pixelSize);
+ }
+
+ private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
+ AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
+ throws XmlPullParserException, IOException {
+ Animator anim = null;
+ ArrayList<Animator> childAnims = null;
+
+ // Make sure we are on a start tag.
+ int type;
+ int depth = parser.getDepth();
+
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+ boolean gotValues = false;
+
+ if (name.equals("objectAnimator")) {
+ anim = loadObjectAnimator(res, theme, attrs, pixelSize);
+ } else if (name.equals("animator")) {
+ anim = loadAnimator(res, theme, attrs, null, pixelSize);
+ } else if (name.equals("set")) {
+ anim = new AnimatorSet();
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.AnimatorSet, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
+ }
+ anim.appendChangingConfigurations(a.getChangingConfigurations());
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
+ createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
+ pixelSize);
+ a.recycle();
+ } else if (name.equals("propertyValuesHolder")) {
+ PropertyValuesHolder[] values = loadValues(res, theme, parser,
+ Xml.asAttributeSet(parser));
+ if (values != null && anim != null && (anim instanceof ValueAnimator)) {
+ ((ValueAnimator) anim).setValues(values);
+ }
+ gotValues = true;
+ } else {
+ throw new RuntimeException("Unknown animator name: " + parser.getName());
+ }
+
+ if (parent != null && !gotValues) {
+ if (childAnims == null) {
+ childAnims = new ArrayList<Animator>();
+ }
+ childAnims.add(anim);
+ }
+ }
+ if (parent != null && childAnims != null) {
+ Animator[] animsArray = new Animator[childAnims.size()];
+ int index = 0;
+ for (Animator a : childAnims) {
+ animsArray[index++] = a;
+ }
+ if (sequenceOrdering == TOGETHER) {
+ parent.playTogether(animsArray);
+ } else {
+ parent.playSequentially(animsArray);
+ }
+ }
+ return anim;
+ }
+
+ private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
+ XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+ ArrayList<PropertyValuesHolder> values = null;
+
+ int type;
+ while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ parser.next();
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (name.equals("propertyValuesHolder")) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
+ }
+ String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
+ int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
+ VALUE_TYPE_UNDEFINED);
+
+ PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
+ if (pvh == null) {
+ pvh = getPVH(a, valueType,
+ R.styleable.PropertyValuesHolder_valueFrom,
+ R.styleable.PropertyValuesHolder_valueTo, propertyName);
+ }
+ if (pvh != null) {
+ if (values == null) {
+ values = new ArrayList<PropertyValuesHolder>();
+ }
+ values.add(pvh);
+ }
+ a.recycle();
+ }
+
+ parser.next();
+ }
+
+ PropertyValuesHolder[] valuesArray = null;
+ if (values != null) {
+ int count = values.size();
+ valuesArray = new PropertyValuesHolder[count];
+ for (int i = 0; i < count; ++i) {
+ valuesArray[i] = values.get(i);
+ }
+ }
+ return valuesArray;
+ }
+
+ // When no value type is provided in keyframe, we need to infer the type from the value. i.e.
+ // if value is defined in the style of a color value, then the color type is returned.
+ // Otherwise, default float type is returned.
+ private static int inferValueTypeOfKeyframe(Resources res, Theme theme, AttributeSet attrs) {
+ int valueType;
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.Keyframe);
+ }
+
+ TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+ boolean hasValue = (keyframeValue != null);
+ // When no value type is provided, check whether it's a color type first.
+ // If not, fall back to default value type (i.e. float type).
+ if (hasValue && isColorType(keyframeValue.type)) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ a.recycle();
+ return valueType;
+ }
+
+ private static int inferValueTypeFromValues(TypedArray styledAttributes, int valueFromId,
+ int valueToId) {
+ TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
+ boolean hasFrom = (tvFrom != null);
+ int fromType = hasFrom ? tvFrom.type : 0;
+ TypedValue tvTo = styledAttributes.peekValue(valueToId);
+ boolean hasTo = (tvTo != null);
+ int toType = hasTo ? tvTo.type : 0;
+
+ int valueType;
+ // Check whether it's color type. If not, fall back to default type (i.e. float type)
+ if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ return valueType;
+ }
+
+ private static void dumpKeyframes(Object[] keyframes, String header) {
+ if (keyframes == null || keyframes.length == 0) {
+ return;
+ }
+ Log.d(TAG, header);
+ int count = keyframes.length;
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = (Keyframe) keyframes[i];
+ Log.d(TAG, "Keyframe " + i + ": fraction " +
+ (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " +
+ ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null"));
+ }
+ }
+
+ // Load property values holder if there are keyframes defined in it. Otherwise return null.
+ private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
+ String propertyName, int valueType)
+ throws XmlPullParserException, IOException {
+
+ PropertyValuesHolder value = null;
+ ArrayList<Keyframe> keyframes = null;
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ String name = parser.getName();
+ if (name.equals("keyframe")) {
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ valueType = inferValueTypeOfKeyframe(res, theme, Xml.asAttributeSet(parser));
+ }
+ Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
+ if (keyframe != null) {
+ if (keyframes == null) {
+ keyframes = new ArrayList<Keyframe>();
+ }
+ keyframes.add(keyframe);
+ }
+ parser.next();
+ }
+ }
+
+ int count;
+ if (keyframes != null && (count = keyframes.size()) > 0) {
+ // make sure we have keyframes at 0 and 1
+ // If we have keyframes with set fractions, add keyframes at start/end
+ // appropriately. If start/end have no set fractions:
+ // if there's only one keyframe, set its fraction to 1 and add one at 0
+ // if >1 keyframe, set the last fraction to 1, the first fraction to 0
+ Keyframe firstKeyframe = keyframes.get(0);
+ Keyframe lastKeyframe = keyframes.get(count - 1);
+ float endFraction = lastKeyframe.getFraction();
+ if (endFraction < 1) {
+ if (endFraction < 0) {
+ lastKeyframe.setFraction(1);
+ } else {
+ keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1));
+ ++count;
+ }
+ }
+ float startFraction = firstKeyframe.getFraction();
+ if (startFraction != 0) {
+ if (startFraction < 0) {
+ firstKeyframe.setFraction(0);
+ } else {
+ keyframes.add(0, createNewKeyframe(firstKeyframe, 0));
+ ++count;
+ }
+ }
+ Keyframe[] keyframeArray = new Keyframe[count];
+ keyframes.toArray(keyframeArray);
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = keyframeArray[i];
+ if (keyframe.getFraction() < 0) {
+ if (i == 0) {
+ keyframe.setFraction(0);
+ } else if (i == count - 1) {
+ keyframe.setFraction(1);
+ } else {
+ // figure out the start/end parameters of the current gap
+ // in fractions and distribute the gap among those keyframes
+ int startIndex = i;
+ int endIndex = i;
+ for (int j = startIndex + 1; j < count - 1; ++j) {
+ if (keyframeArray[j].getFraction() >= 0) {
+ break;
+ }
+ endIndex = j;
+ }
+ float gap = keyframeArray[endIndex + 1].getFraction() -
+ keyframeArray[startIndex - 1].getFraction();
+ distributeKeyframes(keyframeArray, gap, startIndex, endIndex);
+ }
+ }
+ }
+ value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray);
+ if (valueType == VALUE_TYPE_COLOR) {
+ value.setEvaluator(ArgbEvaluator.getInstance());
+ }
+ }
+
+ return value;
+ }
+
+ private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) {
+ return sampleKeyframe.getType() == float.class ?
+ Keyframe.ofFloat(fraction) :
+ (sampleKeyframe.getType() == int.class) ?
+ Keyframe.ofInt(fraction) :
+ Keyframe.ofObject(fraction);
+ }
+
+ /**
+ * Utility function to set fractions on keyframes to cover a gap in which the
+ * fractions are not currently set. Keyframe fractions will be distributed evenly
+ * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap
+ * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the
+ * keyframe before startIndex.
+ * Assumptions:
+ * - First and last keyframe fractions (bounding this spread) are already set. So,
+ * for example, if no fractions are set, we will already set first and last keyframe
+ * fraction values to 0 and 1.
+ * - startIndex must be >0 (which follows from first assumption).
+ * - endIndex must be >= startIndex.
+ *
+ * @param keyframes the array of keyframes
+ * @param gap The total gap we need to distribute
+ * @param startIndex The index of the first keyframe whose fraction must be set
+ * @param endIndex The index of the last keyframe whose fraction must be set
+ */
+ private static void distributeKeyframes(Keyframe[] keyframes, float gap,
+ int startIndex, int endIndex) {
+ int count = endIndex - startIndex + 2;
+ float increment = gap / count;
+ for (int i = startIndex; i <= endIndex; ++i) {
+ keyframes[i].setFraction(keyframes[i-1].getFraction() + increment);
+ }
+ }
+
+ private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
+ int valueType)
+ throws XmlPullParserException, IOException {
+
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.Keyframe);
+ }
+
+ Keyframe keyframe = null;
+
+ float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
+
+ TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+ boolean hasValue = (keyframeValue != null);
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // When no value type is provided, check whether it's a color type first.
+ // If not, fall back to default value type (i.e. float type).
+ if (hasValue && isColorType(keyframeValue.type)) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
+
+ if (hasValue) {
+ switch (valueType) {
+ case VALUE_TYPE_FLOAT:
+ float value = a.getFloat(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofFloat(fraction, value);
+ break;
+ case VALUE_TYPE_COLOR:
+ case VALUE_TYPE_INT:
+ int intValue = a.getInt(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofInt(fraction, intValue);
+ break;
+ }
+ } else {
+ keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
+ Keyframe.ofInt(fraction);
+ }
+
+ final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0);
+ if (resID > 0) {
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ keyframe.setInterpolator(interpolator);
+ }
+ a.recycle();
+
+ return keyframe;
+ }
+
+ private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs,
+ float pathErrorScale) throws NotFoundException {
+ ObjectAnimator anim = new ObjectAnimator();
+
+ loadAnimator(res, theme, attrs, anim, pathErrorScale);
+
+ return anim;
+ }
+
+ /**
+ * Creates a new animation whose parameters come from the specified context
+ * and attributes set.
+ *
+ * @param res The resources
+ * @param attrs The set of attributes holding the animation parameters
+ * @param anim Null if this is a ValueAnimator, otherwise this is an
+ * ObjectAnimator
+ */
+ private static ValueAnimator loadAnimator(Resources res, Theme theme,
+ AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
+ throws NotFoundException {
+ TypedArray arrayAnimator = null;
+ TypedArray arrayObjectAnimator = null;
+
+ if (theme != null) {
+ arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
+ } else {
+ arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator);
+ }
+
+ // If anim is not null, then it is an object animator.
+ if (anim != null) {
+ if (theme != null) {
+ arrayObjectAnimator = theme.obtainStyledAttributes(attrs,
+ R.styleable.PropertyAnimator, 0, 0);
+ } else {
+ arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
+ }
+ anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
+ }
+
+ if (anim == null) {
+ anim = new ValueAnimator();
+ }
+ anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
+
+ parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
+
+ final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
+ if (resID > 0) {
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ if (interpolator instanceof BaseInterpolator) {
+ anim.appendChangingConfigurations(
+ ((BaseInterpolator) interpolator).getChangingConfiguration());
+ }
+ anim.setInterpolator(interpolator);
+ }
+
+ arrayAnimator.recycle();
+ if (arrayObjectAnimator != null) {
+ arrayObjectAnimator.recycle();
+ }
+ return anim;
+ }
+
+ private static @Config int getChangingConfigs(@NonNull Resources resources, @AnyRes int id) {
+ synchronized (sTmpTypedValue) {
+ resources.getValue(id, sTmpTypedValue, true);
+ return sTmpTypedValue.changingConfigurations;
+ }
+ }
+
+ private static boolean isColorType(int type) {
+ return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
+ }
+}
diff --git a/android-34/android/animation/AnimatorListenerAdapter.java b/android-34/android/animation/AnimatorListenerAdapter.java
new file mode 100644
index 0000000..2ecb8c3
--- /dev/null
+++ b/android-34/android/animation/AnimatorListenerAdapter.java
@@ -0,0 +1,68 @@
+/*
+ * 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.animation;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
+ Animator.AnimatorPauseListener {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationPause(Animator animation) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAnimationResume(Animator animation) {
+ }
+}
diff --git a/android-34/android/animation/AnimatorSet.java b/android-34/android/animation/AnimatorSet.java
new file mode 100644
index 0000000..60659dc
--- /dev/null
+++ b/android-34/android/animation/AnimatorSet.java
@@ -0,0 +1,2246 @@
+/*
+ * 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.animation;
+
+import android.app.ActivityThread;
+import android.app.Application;
+import android.os.Build;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongArray;
+import android.view.animation.Animation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * This class plays a set of {@link Animator} objects in the specified order. Animations
+ * can be set up to play together, in sequence, or after a specified delay.
+ *
+ * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>:
+ * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or
+ * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add
+ * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be
+ * used in conjunction with methods in the {@link AnimatorSet.Builder Builder}
+ * class to add animations
+ * one by one.</p>
+ *
+ * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between
+ * its animations. For example, an animation a1 could be set up to start before animation a2, a2
+ * before a3, and a3 before a1. The results of this configuration are undefined, but will typically
+ * result in none of the affected animations being played. Because of this (and because
+ * circular dependencies do not make logical sense anyway), circular dependencies
+ * should be avoided, and the dependency flow of animations should only be in one direction.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code AnimatorSet}, read the
+ * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#choreography">Property
+ * Animation</a> developer guide.</p>
+ * </div>
+ */
+public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
+
+ private static final String TAG = "AnimatorSet";
+ /**
+ * Internal variables
+ * NOTE: This object implements the clone() method, making a deep copy of any referenced
+ * objects. As other non-trivial fields are added to this class, make sure to add logic
+ * to clone() to make deep copies of them.
+ */
+
+ /**
+ * Tracks animations currently being played, so that we know what to
+ * cancel or end when cancel() or end() is called on this AnimatorSet
+ */
+ private ArrayList<Node> mPlayingSet = new ArrayList<Node>();
+
+ /**
+ * Contains all nodes, mapped to their respective Animators. When new
+ * dependency information is added for an Animator, we want to add it
+ * to a single node representing that Animator, not create a new Node
+ * if one already exists.
+ */
+ private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
+
+ /**
+ * Contains the start and end events of all the nodes. All these events are sorted in this list.
+ */
+ private ArrayList<AnimationEvent> mEvents = new ArrayList<>();
+
+ /**
+ * Set of all nodes created for this AnimatorSet. This list is used upon
+ * starting the set, and the nodes are placed in sorted order into the
+ * sortedNodes collection.
+ */
+ private ArrayList<Node> mNodes = new ArrayList<Node>();
+
+ /**
+ * Tracks whether any change has been made to the AnimatorSet, which is then used to
+ * determine whether the dependency graph should be re-constructed.
+ */
+ private boolean mDependencyDirty = false;
+
+ /**
+ * Indicates whether an AnimatorSet has been start()'d, whether or
+ * not there is a nonzero startDelay.
+ */
+ private boolean mStarted = false;
+
+ // The amount of time in ms to delay starting the animation after start() is called
+ private long mStartDelay = 0;
+
+ // Animator used for a nonzero startDelay
+ private ValueAnimator mDelayAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(0);
+
+ // Root of the dependency tree of all the animators in the set. In this tree, parent-child
+ // relationship captures the order of animation (i.e. parent and child will play sequentially),
+ // and sibling relationship indicates "with" relationship, as sibling animators start at the
+ // same time.
+ private Node mRootNode = new Node(mDelayAnim);
+
+ // How long the child animations should last in ms. The default value is negative, which
+ // simply means that there is no duration set on the AnimatorSet. When a real duration is
+ // set, it is passed along to the child animations.
+ private long mDuration = -1;
+
+ // Records the interpolator for the set. Null value indicates that no interpolator
+ // was set on this AnimatorSet, so it should not be passed down to the children.
+ private TimeInterpolator mInterpolator = null;
+
+ // The total duration of finishing all the Animators in the set.
+ private long mTotalDuration = 0;
+
+ // In pre-N releases, calling end() before start() on an animator set is no-op. But that is not
+ // consistent with the behavior for other animator types. In order to keep the behavior
+ // consistent within Animation framework, when end() is called without start(), we will start
+ // the animator set and immediately end it for N and forward.
+ private final boolean mShouldIgnoreEndWithoutStart;
+
+ // In pre-O releases, calling start() doesn't reset all the animators values to start values.
+ // As a result, the start of the animation is inconsistent with what setCurrentPlayTime(0) would
+ // look like on O. Also it is inconsistent with what reverse() does on O, as reverse would
+ // advance all the animations to the right beginning values for before starting to reverse.
+ // From O and forward, we will add an additional step of resetting the animation values (unless
+ // the animation was previously seeked and therefore doesn't start from the beginning).
+ private final boolean mShouldResetValuesAtStart;
+
+ // In pre-O releases, end() may never explicitly called on a child animator. As a result, end()
+ // may not even be properly implemented in a lot of cases. After a few apps crashing on this,
+ // it became necessary to use an sdk target guard for calling end().
+ private final boolean mEndCanBeCalled;
+
+ // The time, in milliseconds, when last frame of the animation came in. -1 when the animation is
+ // not running.
+ private long mLastFrameTime = -1;
+
+ // The time, in milliseconds, when the first frame of the animation came in. This is the
+ // frame before we start counting down the start delay, if any.
+ // -1 when the animation is not running.
+ private long mFirstFrame = -1;
+
+ // The time, in milliseconds, when the first frame of the animation came in.
+ // -1 when the animation is not running.
+ private int mLastEventId = -1;
+
+ // Indicates whether the animation is reversing.
+ private boolean mReversing = false;
+
+ // Indicates whether the animation should register frame callbacks. If false, the animation will
+ // passively wait for an AnimatorSet to pulse it.
+ private boolean mSelfPulse = true;
+
+ // SeekState stores the last seeked play time as well as seek direction.
+ private SeekState mSeekState = new SeekState();
+
+ // Indicates where children animators are all initialized with their start values captured.
+ private boolean mChildrenInitialized = false;
+
+ /**
+ * Set on the next frame after pause() is called, used to calculate a new startTime
+ * or delayStartTime which allows the animator set to continue from the point at which
+ * it was paused. If negative, has not yet been set.
+ */
+ private long mPauseTime = -1;
+
+ /**
+ * The start and stop times of all descendant animators.
+ */
+ private long[] mChildStartAndStopTimes;
+
+ // This is to work around a bug in b/34736819. This needs to be removed once app team
+ // fixes their side.
+ private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mNodeMap.get(animation) == null) {
+ throw new AndroidRuntimeException("Error: animation ended is not in the node map");
+ }
+ mNodeMap.get(animation).mEnded = true;
+
+ }
+ };
+
+ public AnimatorSet() {
+ super();
+ mNodeMap.put(mDelayAnim, mRootNode);
+ mNodes.add(mRootNode);
+ boolean isPreO;
+ // Set the flag to ignore calling end() without start() for pre-N releases
+ Application app = ActivityThread.currentApplication();
+ if (app == null || app.getApplicationInfo() == null) {
+ mShouldIgnoreEndWithoutStart = true;
+ isPreO = true;
+ } else {
+ if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+ mShouldIgnoreEndWithoutStart = true;
+ } else {
+ mShouldIgnoreEndWithoutStart = false;
+ }
+
+ isPreO = app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
+ }
+ mShouldResetValuesAtStart = !isPreO;
+ mEndCanBeCalled = !isPreO;
+ }
+
+ /**
+ * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+ * This is equivalent to calling {@link #play(Animator)} with the first animator in the
+ * set and then {@link Builder#with(Animator)} with each of the other animators. Note that
+ * an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually
+ * start until that delay elapses, which means that if the first animator in the list
+ * supplied to this constructor has a startDelay, none of the other animators will start
+ * until that first animator's startDelay has elapsed.
+ *
+ * @param items The animations that will be started simultaneously.
+ */
+ public void playTogether(Animator... items) {
+ if (items != null) {
+ Builder builder = play(items[0]);
+ for (int i = 1; i < items.length; ++i) {
+ builder.with(items[i]);
+ }
+ }
+ }
+
+ /**
+ * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+ *
+ * @param items The animations that will be started simultaneously.
+ */
+ public void playTogether(Collection<Animator> items) {
+ if (items != null && items.size() > 0) {
+ Builder builder = null;
+ for (Animator anim : items) {
+ if (builder == null) {
+ builder = play(anim);
+ } else {
+ builder.with(anim);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets up this AnimatorSet to play each of the supplied animations when the
+ * previous animation ends.
+ *
+ * @param items The animations that will be started one after another.
+ */
+ public void playSequentially(Animator... items) {
+ if (items != null) {
+ if (items.length == 1) {
+ play(items[0]);
+ } else {
+ for (int i = 0; i < items.length - 1; ++i) {
+ play(items[i]).before(items[i + 1]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets up this AnimatorSet to play each of the supplied animations when the
+ * previous animation ends.
+ *
+ * @param items The animations that will be started one after another.
+ */
+ public void playSequentially(List<Animator> items) {
+ if (items != null && items.size() > 0) {
+ if (items.size() == 1) {
+ play(items.get(0));
+ } else {
+ for (int i = 0; i < items.size() - 1; ++i) {
+ play(items.get(i)).before(items.get(i + 1));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the current list of child Animator objects controlled by this
+ * AnimatorSet. This is a copy of the internal list; modifications to the returned list
+ * will not affect the AnimatorSet, although changes to the underlying Animator objects
+ * will affect those objects being managed by the AnimatorSet.
+ *
+ * @return ArrayList<Animator> The list of child animations of this AnimatorSet.
+ */
+ public ArrayList<Animator> getChildAnimations() {
+ ArrayList<Animator> childList = new ArrayList<Animator>();
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ childList.add(node.mAnimation);
+ }
+ }
+ return childList;
+ }
+
+ /**
+ * Sets the target object for all current {@link #getChildAnimations() child animations}
+ * of this AnimatorSet that take targets ({@link ObjectAnimator} and
+ * AnimatorSet).
+ *
+ * @param target The object being animated
+ */
+ @Override
+ public void setTarget(Object target) {
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ Animator animation = node.mAnimation;
+ if (animation instanceof AnimatorSet) {
+ ((AnimatorSet)animation).setTarget(target);
+ } else if (animation instanceof ObjectAnimator) {
+ ((ObjectAnimator)animation).setTarget(target);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getChangingConfigurations() {
+ int conf = super.getChangingConfigurations();
+ final int nodeCount = mNodes.size();
+ for (int i = 0; i < nodeCount; i ++) {
+ conf |= mNodes.get(i).mAnimation.getChangingConfigurations();
+ }
+ return conf;
+ }
+
+ /**
+ * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
+ * of this AnimatorSet. The default value is null, which means that no interpolator
+ * is set on this AnimatorSet. Setting the interpolator to any non-null value
+ * will cause that interpolator to be set on the child animations
+ * when the set is started.
+ *
+ * @param interpolator the interpolator to be used by each child animation of this AnimatorSet
+ */
+ @Override
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * This method creates a <code>Builder</code> object, which is used to
+ * set up playing constraints. This initial <code>play()</code> method
+ * tells the <code>Builder</code> the animation that is the dependency for
+ * the succeeding commands to the <code>Builder</code>. For example,
+ * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play
+ * <code>a1</code> and <code>a2</code> at the same time,
+ * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play
+ * <code>a1</code> first, followed by <code>a2</code>, and
+ * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play
+ * <code>a2</code> first, followed by <code>a1</code>.
+ *
+ * <p>Note that <code>play()</code> is the only way to tell the
+ * <code>Builder</code> the animation upon which the dependency is created,
+ * so successive calls to the various functions in <code>Builder</code>
+ * will all refer to the initial parameter supplied in <code>play()</code>
+ * as the dependency of the other animations. For example, calling
+ * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
+ * and <code>a3</code> when a1 ends; it does not set up a dependency between
+ * <code>a2</code> and <code>a3</code>.</p>
+ *
+ * @param anim The animation that is the dependency used in later calls to the
+ * methods in the returned <code>Builder</code> object. A null parameter will result
+ * in a null <code>Builder</code> return value.
+ * @return Builder The object that constructs the AnimatorSet based on the dependencies
+ * outlined in the calls to <code>play</code> and the other methods in the
+ * <code>Builder</code object.
+ */
+ public Builder play(Animator anim) {
+ if (anim != null) {
+ return new Builder(anim);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it
+ * is responsible for.</p>
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void cancel() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (isStarted() || mStartListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_CANCEL, false);
+ callOnPlayingSet(Animator::cancel);
+ mPlayingSet.clear();
+ endAnimation();
+ }
+ }
+
+ /**
+ * Calls consumer on every Animator of mPlayingSet.
+ *
+ * @param consumer The method to call on every Animator of mPlayingSet.
+ */
+ private void callOnPlayingSet(Consumer<Animator> consumer) {
+ final ArrayList<Node> list = mPlayingSet;
+ final int size = list.size();
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < size; i++) {
+ final Animator animator = list.get(i).mAnimation;
+ consumer.accept(animator);
+ }
+ }
+
+ // Force all the animations to end when the duration scale is 0.
+ private void forceToEnd() {
+ if (mEndCanBeCalled) {
+ end();
+ return;
+ }
+
+ // Note: we don't want to combine this case with the end() method below because in
+ // the case of developer calling end(), we still need to make sure end() is explicitly
+ // called on the child animators to maintain the old behavior.
+ if (mReversing) {
+ handleAnimationEvents(mLastEventId, 0, getTotalDuration());
+ } else {
+ long zeroScalePlayTime = getTotalDuration();
+ if (zeroScalePlayTime == DURATION_INFINITE) {
+ // Use a large number for the play time.
+ zeroScalePlayTime = Integer.MAX_VALUE;
+ }
+ handleAnimationEvents(mLastEventId, mEvents.size() - 1, zeroScalePlayTime);
+ }
+ mPlayingSet.clear();
+ endAnimation();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
+ * responsible for.</p>
+ */
+ @Override
+ public void end() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (mShouldIgnoreEndWithoutStart && !isStarted()) {
+ return;
+ }
+ if (isStarted()) {
+ mStarted = false; // don't allow reentrancy
+ // Iterate the animations that haven't finished or haven't started, and end them.
+ if (mReversing) {
+ // Between start() and first frame, mLastEventId would be unset (i.e. -1)
+ mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
+ for (int eventId = mLastEventId - 1; eventId >= 0; eventId--) {
+ AnimationEvent event = mEvents.get(eventId);
+ Animator anim = event.mNode.mAnimation;
+ if (mNodeMap.get(anim).mEnded) {
+ continue;
+ }
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ anim.reverse();
+ } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && anim.isStarted()) {
+ // Make sure anim hasn't finished before calling end() so that we don't end
+ // already ended animations, which will cause start and end callbacks to be
+ // triggered again.
+ anim.end();
+ }
+ }
+ } else {
+ for (int eventId = mLastEventId + 1; eventId < mEvents.size(); eventId++) {
+ // Avoid potential reentrant loop caused by child animators manipulating
+ // AnimatorSet's lifecycle (i.e. not a recommended approach).
+ AnimationEvent event = mEvents.get(eventId);
+ Animator anim = event.mNode.mAnimation;
+ if (mNodeMap.get(anim).mEnded) {
+ continue;
+ }
+ if (event.mEvent == AnimationEvent.ANIMATION_START) {
+ anim.start();
+ } else if (event.mEvent == AnimationEvent.ANIMATION_END && anim.isStarted()) {
+ // Make sure anim hasn't finished before calling end() so that we don't end
+ // already ended animations, which will cause start and end callbacks to be
+ // triggered again.
+ anim.end();
+ }
+ }
+ }
+ }
+ endAnimation();
+ }
+
+ /**
+ * Returns true if any of the child animations of this AnimatorSet have been started and have
+ * not yet ended. Child animations will not be started until the AnimatorSet has gone past
+ * its initial delay set through {@link #setStartDelay(long)}.
+ *
+ * @return Whether this AnimatorSet has gone past the initial delay, and at least one child
+ * animation has been started and not yet ended.
+ */
+ @Override
+ public boolean isRunning() {
+ if (mStartDelay == 0) {
+ return mStarted;
+ }
+ return mLastFrameTime > 0;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called.
+ *
+ * @return the number of milliseconds to delay running the animation
+ */
+ @Override
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called. Note that the start delay should always be non-negative. Any
+ * negative start delay will be clamped to 0 on N and above.
+ *
+ * @param startDelay The amount of the delay, in milliseconds
+ */
+ @Override
+ public void setStartDelay(long startDelay) {
+ // Clamp start delay to non-negative range.
+ if (startDelay < 0) {
+ Log.w(TAG, "Start delay should always be non-negative");
+ startDelay = 0;
+ }
+ long delta = startDelay - mStartDelay;
+ if (delta == 0) {
+ return;
+ }
+ mStartDelay = startDelay;
+ if (!mDependencyDirty) {
+ // Dependency graph already constructed, update all the nodes' start/end time
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node == mRootNode) {
+ node.mEndTime = mStartDelay;
+ } else {
+ node.mStartTime = node.mStartTime == DURATION_INFINITE ?
+ DURATION_INFINITE : node.mStartTime + delta;
+ node.mEndTime = node.mEndTime == DURATION_INFINITE ?
+ DURATION_INFINITE : node.mEndTime + delta;
+ }
+ }
+ // Update total duration, if necessary.
+ if (mTotalDuration != DURATION_INFINITE) {
+ mTotalDuration += delta;
+ }
+ }
+ }
+
+ /**
+ * Gets the length of each of the child animations of this AnimatorSet. This value may
+ * be less than 0, which indicates that no duration has been set on this AnimatorSet
+ * and each of the child animations will use their own duration.
+ *
+ * @return The length of the animation, in milliseconds, of each of the child
+ * animations of this AnimatorSet.
+ */
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Sets the length of each of the current child animations of this AnimatorSet. By default,
+ * each child animation will use its own duration. If the duration is set on the AnimatorSet,
+ * then each child animation inherits this duration.
+ *
+ * @param duration The length of the animation, in milliseconds, of each of the child
+ * animations of this AnimatorSet.
+ */
+ @Override
+ public AnimatorSet setDuration(long duration) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("duration must be a value of zero or greater");
+ }
+ mDependencyDirty = true;
+ // Just record the value for now - it will be used later when the AnimatorSet starts
+ mDuration = duration;
+ return this;
+ }
+
+ @Override
+ public void setupStartValues() {
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ node.mAnimation.setupStartValues();
+ }
+ }
+ }
+
+ @Override
+ public void setupEndValues() {
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ node.mAnimation.setupEndValues();
+ }
+ }
+ }
+
+ @Override
+ public void pause() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ boolean previouslyPaused = mPaused;
+ super.pause();
+ if (!previouslyPaused && mPaused) {
+ mPauseTime = -1;
+ callOnPlayingSet(Animator::pause);
+ }
+ }
+
+ @Override
+ public void resume() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ boolean previouslyPaused = mPaused;
+ super.resume();
+ if (previouslyPaused && !mPaused) {
+ if (mPauseTime >= 0) {
+ addAnimationCallback(0);
+ }
+ callOnPlayingSet(Animator::resume);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which
+ * it is responsible. The details of when exactly those animations are started depends on
+ * the dependency relationships that have been set up between the animations.
+ *
+ * <b>Note:</b> Manipulating AnimatorSet's lifecycle in the child animators' listener callbacks
+ * will lead to undefined behaviors. Also, AnimatorSet will ignore any seeking in the child
+ * animators once {@link #start()} is called.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void start() {
+ start(false, true);
+ }
+
+ @Override
+ void startWithoutPulsing(boolean inReverse) {
+ start(inReverse, false);
+ }
+
+ private void initAnimation() {
+ if (mInterpolator != null) {
+ for (int i = 0; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ node.mAnimation.setInterpolator(mInterpolator);
+ }
+ }
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ }
+
+ private void start(boolean inReverse, boolean selfPulse) {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (inReverse == mReversing && selfPulse == mSelfPulse && mStarted) {
+ // It is already started
+ return;
+ }
+ mStarted = true;
+ mSelfPulse = selfPulse;
+ mPaused = false;
+ mPauseTime = -1;
+
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ node.mEnded = false;
+ node.mAnimation.setAllowRunningAsynchronously(false);
+ }
+
+ initAnimation();
+ if (inReverse && !canReverse()) {
+ throw new UnsupportedOperationException("Cannot reverse infinite AnimatorSet");
+ }
+
+ mReversing = inReverse;
+
+ // Now that all dependencies are set up, start the animations that should be started.
+ boolean isEmptySet = isEmptySet(this);
+ if (!isEmptySet) {
+ startAnimation();
+ }
+
+ notifyStartListeners(inReverse);
+ if (isEmptySet) {
+ // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
+ // onAnimationEnd() right away.
+ end();
+ }
+ }
+
+ // Returns true if set is empty or contains nothing but animator sets with no start delay.
+ private static boolean isEmptySet(AnimatorSet set) {
+ if (set.getStartDelay() > 0) {
+ return false;
+ }
+ for (int i = 0; i < set.getChildAnimations().size(); i++) {
+ Animator anim = set.getChildAnimations().get(i);
+ if (!(anim instanceof AnimatorSet)) {
+ // Contains non-AnimatorSet, not empty.
+ return false;
+ } else {
+ if (!isEmptySet((AnimatorSet) anim)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private void updateAnimatorsDuration() {
+ if (mDuration >= 0) {
+ // If the duration was set on this AnimatorSet, pass it along to all child animations
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+ // insert "play-after" delays
+ node.mAnimation.setDuration(mDuration);
+ }
+ }
+ mDelayAnim.setDuration(mStartDelay);
+ }
+
+ @Override
+ void skipToEndValue(boolean inReverse) {
+ // This makes sure the animation events are sorted an up to date.
+ initAnimation();
+ initChildren();
+
+ // Calling skip to the end in the sequence that they would be called in a forward/reverse
+ // run, such that the sequential animations modifying the same property would have
+ // the right value in the end.
+ if (inReverse) {
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ event.mNode.mAnimation.skipToEndValue(true);
+ }
+ }
+ } else {
+ for (int i = 0; i < mEvents.size(); i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ event.mNode.mAnimation.skipToEndValue(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal only.
+ *
+ * This method sets the animation values based on the play time. It also fast forward or
+ * backward all the child animations progress accordingly.
+ *
+ * This method is also responsible for calling
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
+ * as needed, based on the last play time and current play time.
+ */
+ private void animateBasedOnPlayTime(
+ long currentPlayTime,
+ long lastPlayTime,
+ boolean inReverse,
+ boolean notify
+ ) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
+ throw new UnsupportedOperationException("Error: Play time should never be negative.");
+ }
+ // TODO: take into account repeat counts and repeat callback when repeat is implemented.
+
+ if (inReverse) {
+ long duration = getTotalDuration();
+ if (duration == DURATION_INFINITE) {
+ throw new UnsupportedOperationException(
+ "Cannot reverse AnimatorSet with infinite duration"
+ );
+ }
+ // Convert the play times to the forward direction.
+ currentPlayTime = Math.min(currentPlayTime, duration);
+ currentPlayTime = duration - currentPlayTime;
+ lastPlayTime = duration - lastPlayTime;
+ }
+
+ long[] startEndTimes = ensureChildStartAndEndTimes();
+ int index = findNextIndex(lastPlayTime, startEndTimes);
+ int endIndex = findNextIndex(currentPlayTime, startEndTimes);
+
+ // Change values at the start/end times so that values are set in the right order.
+ // We don't want an animator that would finish before another to override the value
+ // set by another animator that finishes earlier.
+ if (currentPlayTime >= lastPlayTime) {
+ while (index < endIndex) {
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime, notify);
+ animateValuesInRange(playTime, lastPlayTime, notify);
+ lastPlayTime = playTime;
+ }
+ index++;
+ }
+ } else {
+ while (index > endIndex) {
+ index--;
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime, notify);
+ animateValuesInRange(playTime, lastPlayTime, notify);
+ lastPlayTime = playTime;
+ }
+ }
+ }
+ if (currentPlayTime != lastPlayTime) {
+ animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
+ animateValuesInRange(currentPlayTime, lastPlayTime, notify);
+ }
+ }
+
+ /**
+ * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
+ * is returned. Otherwise, it returns the index at which it would be placed if it were
+ * to be inserted.
+ */
+ private int findNextIndex(long playTime, long[] startEndTimes) {
+ int index = Arrays.binarySearch(startEndTimes, playTime);
+ if (index < 0) {
+ index = -index - 1;
+ } else {
+ index++;
+ }
+ return index;
+ }
+
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ initAnimation();
+
+ if (lastPlayTime > currentPlayTime) {
+ if (notify) {
+ notifyStartListeners(true);
+ }
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (currentPlayTime <= start && start < lastPlayTime) {
+ animator.animateSkipToEnds(
+ 0,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ if (notify) {
+ mPlayingSet.remove(node);
+ }
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ if (notify && !mPlayingSet.contains(node)) {
+ mPlayingSet.add(node);
+ }
+ }
+ }
+ }
+ if (currentPlayTime <= 0 && notify) {
+ notifyEndListeners(true);
+ }
+ } else {
+ if (notify) {
+ notifyStartListeners(false);
+ }
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (lastPlayTime < end && end <= currentPlayTime) {
+ animator.animateSkipToEnds(
+ end - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ if (notify) {
+ mPlayingSet.remove(node);
+ }
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ if (notify && !mPlayingSet.contains(node)) {
+ mPlayingSet.add(node);
+ }
+ }
+ }
+ }
+ if (currentPlayTime >= getTotalDuration() && notify) {
+ notifyEndListeners(false);
+ }
+ }
+ }
+
+ @Override
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ initAnimation();
+
+ if (notify) {
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else {
+ long duration = getTotalDuration();
+ if (duration >= 0
+ && (lastPlayTime > duration || (lastPlayTime == duration
+ && currentPlayTime < duration))
+ ) {
+ notifyStartListeners(true);
+ }
+ }
+ }
+
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if ((start < currentPlayTime && currentPlayTime < end)
+ || (start == currentPlayTime && lastPlayTime < start)
+ || (end == currentPlayTime && lastPlayTime > end)
+ ) {
+ animator.animateValuesInRange(
+ currentPlayTime - node.mStartTime,
+ Math.max(-1, lastPlayTime - node.mStartTime),
+ notify
+ );
+ }
+ }
+ }
+ }
+
+ private long[] ensureChildStartAndEndTimes() {
+ if (mChildStartAndStopTimes == null) {
+ LongArray startAndEndTimes = new LongArray();
+ getStartAndEndTimes(startAndEndTimes, 0);
+ long[] times = startAndEndTimes.toArray();
+ Arrays.sort(times);
+ mChildStartAndStopTimes = times;
+ }
+ return mChildStartAndStopTimes;
+ }
+
+ @Override
+ void getStartAndEndTimes(LongArray times, long offset) {
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && event.mNode.mStartTime != DURATION_INFINITE
+ ) {
+ event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+ }
+ }
+ }
+
+ @Override
+ boolean isInitialized() {
+ if (mChildrenInitialized) {
+ return true;
+ }
+
+ boolean allInitialized = true;
+ for (int i = 0; i < mNodes.size(); i++) {
+ if (!mNodes.get(i).mAnimation.isInitialized()) {
+ allInitialized = false;
+ break;
+ }
+ }
+ mChildrenInitialized = allInitialized;
+ return mChildrenInitialized;
+ }
+
+ /**
+ * Sets the position of the animation to the specified point in time. This time should
+ * be between 0 and the total duration of the animation, including any repetition. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this time; it will simply set the time to this value and perform any appropriate
+ * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+ * will set the current playing time to this value and continue playing from that point.
+ * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
+ * that hasn't been {@link #start()}ed, will issue
+ * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
+ * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
+ * events.
+ *
+ * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+ * Unless the animation is reversing, the playtime is considered the time since
+ * the end of the start delay of the AnimatorSet in a forward playing direction.
+ *
+ */
+ public void setCurrentPlayTime(long playTime) {
+ if (mReversing && getTotalDuration() == DURATION_INFINITE) {
+ // Should never get here
+ throw new UnsupportedOperationException("Error: Cannot seek in reverse in an infinite"
+ + " AnimatorSet");
+ }
+
+ if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
+ || playTime < 0) {
+ throw new UnsupportedOperationException("Error: Play time should always be in between"
+ + " 0 and duration.");
+ }
+
+ initAnimation();
+
+ long lastPlayTime = mSeekState.getPlayTime();
+ if (!isStarted() || isPaused()) {
+ if (mReversing && !isStarted()) {
+ throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+ + " should not be set when AnimatorSet is not started.");
+ }
+ if (!mSeekState.isActive()) {
+ findLatestEventIdForTime(0);
+ initChildren();
+ // Set all the values to start values.
+ skipToEndValue(!mReversing);
+ mSeekState.setPlayTime(0, mReversing);
+ }
+ }
+ mSeekState.setPlayTime(playTime, mReversing);
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+ }
+
+ /**
+ * Returns the milliseconds elapsed since the start of the animation.
+ *
+ * <p>For ongoing animations, this method returns the current progress of the animation in
+ * terms of play time. For an animation that has not yet been started: if the animation has been
+ * seeked to a certain time via {@link #setCurrentPlayTime(long)}, the seeked play time will
+ * be returned; otherwise, this method will return 0.
+ *
+ * @return the current position in time of the animation in milliseconds
+ */
+ public long getCurrentPlayTime() {
+ if (mSeekState.isActive()) {
+ return mSeekState.getPlayTime();
+ }
+ if (mLastFrameTime == -1) {
+ // Not yet started or during start delay
+ return 0;
+ }
+ float durationScale = ValueAnimator.getDurationScale();
+ durationScale = durationScale == 0 ? 1 : durationScale;
+ if (mReversing) {
+ return (long) ((mLastFrameTime - mFirstFrame) / durationScale);
+ } else {
+ return (long) ((mLastFrameTime - mFirstFrame - mStartDelay) / durationScale);
+ }
+ }
+
+ private void initChildren() {
+ if (!isInitialized()) {
+ mChildrenInitialized = true;
+
+ // We have to initialize all the start values so that they are based on the previous
+ // values.
+ long[] times = ensureChildStartAndEndTimes();
+
+ long previousTime = -1;
+ for (long time : times) {
+ animateBasedOnPlayTime(time, previousTime, false, false);
+ previousTime = time;
+ }
+ }
+ }
+
+ /**
+ * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
+ * base.
+ * @return
+ * @hide
+ */
+ @Override
+ public boolean doAnimationFrame(long frameTime) {
+ float durationScale = ValueAnimator.getDurationScale();
+ if (durationScale == 0f) {
+ // Duration scale is 0, end the animation right away.
+ forceToEnd();
+ return true;
+ }
+
+ // After the first frame comes in, we need to wait for start delay to pass before updating
+ // any animation values.
+ if (mFirstFrame < 0) {
+ mFirstFrame = frameTime;
+ }
+
+ // Handle pause/resume
+ if (mPaused) {
+ // Note: Child animations don't receive pause events. Since it's never a contract that
+ // the child animators will be paused when set is paused, this is unlikely to be an
+ // issue.
+ mPauseTime = frameTime;
+ removeAnimationCallback();
+ return false;
+ } else if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mFirstFrame += (frameTime - mPauseTime);
+ mPauseTime = -1;
+ }
+
+ // Continue at seeked position
+ if (mSeekState.isActive()) {
+ mSeekState.updateSeekDirection(mReversing);
+ if (mReversing) {
+ mFirstFrame = (long) (frameTime - mSeekState.getPlayTime() * durationScale);
+ } else {
+ mFirstFrame = (long) (frameTime - (mSeekState.getPlayTime() + mStartDelay)
+ * durationScale);
+ }
+ mSeekState.reset();
+ }
+
+ if (!mReversing && frameTime < mFirstFrame + mStartDelay * durationScale) {
+ // Still during start delay in a forward playing case.
+ return false;
+ }
+
+ // From here on, we always use unscaled play time. Note this unscaled playtime includes
+ // the start delay.
+ long unscaledPlayTime = (long) ((frameTime - mFirstFrame) / durationScale);
+ mLastFrameTime = frameTime;
+
+ // 1. Pulse the animators that will start or end in this frame
+ // 2. Pulse the animators that will finish in a later frame
+ int latestId = findLatestEventIdForTime(unscaledPlayTime);
+ int startId = mLastEventId;
+
+ handleAnimationEvents(startId, latestId, unscaledPlayTime);
+
+ mLastEventId = latestId;
+
+ // Pump a frame to the on-going animators
+ for (int i = 0; i < mPlayingSet.size(); i++) {
+ Node node = mPlayingSet.get(i);
+ if (!node.mEnded) {
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
+ }
+ }
+
+ // Remove all the finished anims
+ for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
+ if (mPlayingSet.get(i).mEnded) {
+ mPlayingSet.remove(i);
+ }
+ }
+
+ boolean finished = false;
+ if (mReversing) {
+ if (mPlayingSet.size() == 1 && mPlayingSet.get(0) == mRootNode) {
+ // The only animation that is running is the delay animation.
+ finished = true;
+ } else if (mPlayingSet.isEmpty() && mLastEventId < 3) {
+ // The only remaining animation is the delay animation
+ finished = true;
+ }
+ } else {
+ finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1;
+ }
+
+ if (finished) {
+ endAnimation();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void commitAnimationFrame(long frameTime) {
+ // No op.
+ }
+
+ @Override
+ boolean pulseAnimationFrame(long frameTime) {
+ return doAnimationFrame(frameTime);
+ }
+
+ /**
+ * When playing forward, we call start() at the animation's scheduled start time, and make sure
+ * to pump a frame at the animation's scheduled end time.
+ *
+ * When playing in reverse, we should reverse the animation when we hit animation's end event,
+ * and expect the animation to end at the its delay ended event, rather than start event.
+ */
+ private void handleAnimationEvents(int startId, int latestId, long playTime) {
+ if (mReversing) {
+ startId = startId == -1 ? mEvents.size() : startId;
+ for (int i = startId - 1; i >= latestId; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ if (node.mAnimation.isStarted()) {
+ // If the animation has already been started before its due time (i.e.
+ // the child animator is being manipulated outside of the AnimatorSet), we
+ // need to cancel the animation to reset the internal state (e.g. frame
+ // time tracking) and remove the self pulsing callbacks
+ node.mAnimation.cancel();
+ }
+ node.mEnded = false;
+ mPlayingSet.add(event.mNode);
+ node.mAnimation.startWithoutPulsing(true);
+ pulseFrame(node, 0);
+ } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
+ // end event:
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ }
+ }
+ } else {
+ for (int i = startId + 1; i <= latestId; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_START) {
+ mPlayingSet.add(event.mNode);
+ if (node.mAnimation.isStarted()) {
+ // If the animation has already been started before its due time (i.e.
+ // the child animator is being manipulated outside of the AnimatorSet), we
+ // need to cancel the animation to reset the internal state (e.g. frame
+ // time tracking) and remove the self pulsing callbacks
+ node.mAnimation.cancel();
+ }
+ node.mEnded = false;
+ node.mAnimation.startWithoutPulsing(false);
+ pulseFrame(node, 0);
+ } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
+ // start event:
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
+ }
+ }
+ }
+ }
+
+ /**
+ * This method pulses frames into child animations. It scales the input animation play time
+ * with the duration scale and pass that to the child animation via pulseAnimationFrame(long).
+ *
+ * @param node child animator node
+ * @param animPlayTime unscaled play time (including start delay) for the child animator
+ */
+ private void pulseFrame(Node node, long animPlayTime) {
+ if (!node.mEnded) {
+ float durationScale = ValueAnimator.getDurationScale();
+ durationScale = durationScale == 0 ? 1 : durationScale;
+ node.mEnded = node.mAnimation.pulseAnimationFrame(
+ (long) (animPlayTime * durationScale));
+ }
+ }
+
+ private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
+ return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
+ }
+
+ private long getPlayTimeForNodeIncludingDelay(
+ long overallPlayTime,
+ Node node,
+ boolean inReverse
+ ) {
+ if (inReverse) {
+ overallPlayTime = getTotalDuration() - overallPlayTime;
+ return node.mEndTime - overallPlayTime;
+ } else {
+ return overallPlayTime - node.mStartTime;
+ }
+ }
+
+ private void startAnimation() {
+ addAnimationEndListener();
+
+ // Register animation callback
+ addAnimationCallback(0);
+
+ if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) {
+ // Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case
+ // the same as no seeking at all.
+ mSeekState.reset();
+ }
+ // Set the child animators to the right end:
+ if (mShouldResetValuesAtStart) {
+ initChildren();
+ skipToEndValue(!mReversing);
+ }
+
+ if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
+ long playTime;
+ // If no delay, we need to call start on the first animations to be consistent with old
+ // behavior.
+ if (mSeekState.isActive()) {
+ mSeekState.updateSeekDirection(mReversing);
+ playTime = mSeekState.getPlayTime();
+ } else {
+ playTime = 0;
+ }
+ int toId = findLatestEventIdForTime(playTime);
+ handleAnimationEvents(-1, toId, playTime);
+ for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
+ if (mPlayingSet.get(i).mEnded) {
+ mPlayingSet.remove(i);
+ }
+ }
+ mLastEventId = toId;
+ }
+ }
+
+ // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had
+ // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed.
+ private void addAnimationEndListener() {
+ for (int i = 1; i < mNodes.size(); i++) {
+ mNodes.get(i).mAnimation.addListener(mAnimationEndListener);
+ }
+ }
+
+ private void removeAnimationEndListener() {
+ for (int i = 1; i < mNodes.size(); i++) {
+ mNodes.get(i).mAnimation.removeListener(mAnimationEndListener);
+ }
+ }
+
+ private int findLatestEventIdForTime(long currentPlayTime) {
+ int size = mEvents.size();
+ int latestId = mLastEventId;
+ // Call start on the first animations now to be consistent with the old behavior
+ if (mReversing) {
+ currentPlayTime = getTotalDuration() - currentPlayTime;
+ mLastEventId = mLastEventId == -1 ? size : mLastEventId;
+ for (int j = mLastEventId - 1; j >= 0; j--) {
+ AnimationEvent event = mEvents.get(j);
+ if (event.getTime() >= currentPlayTime) {
+ latestId = j;
+ }
+ }
+ } else {
+ for (int i = mLastEventId + 1; i < size; i++) {
+ AnimationEvent event = mEvents.get(i);
+ // TODO: need a function that accounts for infinite duration to compare time
+ if (event.getTime() != DURATION_INFINITE && event.getTime() <= currentPlayTime) {
+ latestId = i;
+ }
+ }
+ }
+ return latestId;
+ }
+
+ private void endAnimation() {
+ mStarted = false;
+ mLastFrameTime = -1;
+ mFirstFrame = -1;
+ mLastEventId = -1;
+ mPaused = false;
+ mPauseTime = -1;
+ mSeekState.reset();
+ mPlayingSet.clear();
+
+ // No longer receive callbacks
+ removeAnimationCallback();
+ notifyEndListeners(mReversing);
+ removeAnimationEndListener();
+ mSelfPulse = true;
+ mReversing = false;
+ }
+
+ private void removeAnimationCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.removeCallback(this);
+ }
+
+ private void addAnimationCallback(long delay) {
+ if (!mSelfPulse) {
+ return;
+ }
+ AnimationHandler handler = AnimationHandler.getInstance();
+ handler.addAnimationFrameCallback(this, delay);
+ }
+
+ @Override
+ public AnimatorSet clone() {
+ final AnimatorSet anim = (AnimatorSet) super.clone();
+ /*
+ * The basic clone() operation copies all items. This doesn't work very well for
+ * AnimatorSet, because it will copy references that need to be recreated and state
+ * that may not apply. What we need to do now is put the clone in an uninitialized
+ * state, with fresh, empty data structures. Then we will build up the nodes list
+ * manually, as we clone each Node (and its animation). The clone will then be sorted,
+ * and will populate any appropriate lists, when it is started.
+ */
+ final int nodeCount = mNodes.size();
+ anim.mStarted = false;
+ anim.mLastFrameTime = -1;
+ anim.mFirstFrame = -1;
+ anim.mLastEventId = -1;
+ anim.mPaused = false;
+ anim.mPauseTime = -1;
+ anim.mSeekState = new SeekState();
+ anim.mSelfPulse = true;
+ anim.mPlayingSet = new ArrayList<Node>();
+ anim.mNodeMap = new ArrayMap<Animator, Node>();
+ anim.mNodes = new ArrayList<Node>(nodeCount);
+ anim.mEvents = new ArrayList<AnimationEvent>();
+ anim.mAnimationEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (anim.mNodeMap.get(animation) == null) {
+ throw new AndroidRuntimeException("Error: animation ended is not in the node"
+ + " map");
+ }
+ anim.mNodeMap.get(animation).mEnded = true;
+
+ }
+ };
+ anim.mReversing = false;
+ anim.mDependencyDirty = true;
+
+ // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
+ // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
+ // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
+
+ HashMap<Node, Node> clonesMap = new HashMap<>(nodeCount);
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
+ Node nodeClone = node.clone();
+ // Remove the old internal listener from the cloned child
+ nodeClone.mAnimation.removeListener(mAnimationEndListener);
+ clonesMap.put(node, nodeClone);
+ anim.mNodes.add(nodeClone);
+ anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
+ }
+
+ anim.mRootNode = clonesMap.get(mRootNode);
+ anim.mDelayAnim = (ValueAnimator) anim.mRootNode.mAnimation;
+
+ // Now that we've cloned all of the nodes, we're ready to walk through their
+ // dependencies, mapping the old dependencies to the new nodes
+ for (int i = 0; i < nodeCount; i++) {
+ Node node = mNodes.get(i);
+ // Update dependencies for node's clone
+ Node nodeClone = clonesMap.get(node);
+ nodeClone.mLatestParent = node.mLatestParent == null
+ ? null : clonesMap.get(node.mLatestParent);
+ int size = node.mChildNodes == null ? 0 : node.mChildNodes.size();
+ for (int j = 0; j < size; j++) {
+ nodeClone.mChildNodes.set(j, clonesMap.get(node.mChildNodes.get(j)));
+ }
+ size = node.mSiblings == null ? 0 : node.mSiblings.size();
+ for (int j = 0; j < size; j++) {
+ nodeClone.mSiblings.set(j, clonesMap.get(node.mSiblings.get(j)));
+ }
+ size = node.mParents == null ? 0 : node.mParents.size();
+ for (int j = 0; j < size; j++) {
+ nodeClone.mParents.set(j, clonesMap.get(node.mParents.get(j)));
+ }
+ }
+ return anim;
+ }
+
+
+ /**
+ * AnimatorSet is only reversible when the set contains no sequential animation, and no child
+ * animators have a start delay.
+ * @hide
+ */
+ @Override
+ public boolean canReverse() {
+ return getTotalDuration() != DURATION_INFINITE;
+ }
+
+ /**
+ * Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time
+ * using {@link #setCurrentPlayTime(long)}, it will play backwards from the point seeked when
+ * reverse was called. Otherwise, then it will start from the end and play backwards. This
+ * behavior is only set for the current animation; future playing of the animation will use the
+ * default behavior of playing forward.
+ * <p>
+ * Note: reverse is not supported for infinite AnimatorSet.
+ */
+ @Override
+ public void reverse() {
+ start(true, true);
+ }
+
+ @Override
+ public String toString() {
+ String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{";
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ returnVal += "\n " + node.mAnimation.toString();
+ }
+ return returnVal + "\n}";
+ }
+
+ private void printChildCount() {
+ // Print out the child count through a level traverse.
+ ArrayList<Node> list = new ArrayList<>(mNodes.size());
+ list.add(mRootNode);
+ Log.d(TAG, "Current tree: ");
+ int index = 0;
+ while (index < list.size()) {
+ int listSize = list.size();
+ StringBuilder builder = new StringBuilder();
+ for (; index < listSize; index++) {
+ Node node = list.get(index);
+ int num = 0;
+ if (node.mChildNodes != null) {
+ for (int i = 0; i < node.mChildNodes.size(); i++) {
+ Node child = node.mChildNodes.get(i);
+ if (child.mLatestParent == node) {
+ num++;
+ list.add(child);
+ }
+ }
+ }
+ builder.append(" ");
+ builder.append(num);
+ }
+ Log.d(TAG, builder.toString());
+ }
+ }
+
+ private void createDependencyGraph() {
+ if (!mDependencyDirty) {
+ // Check whether any duration of the child animations has changed
+ boolean durationChanged = false;
+ for (int i = 0; i < mNodes.size(); i++) {
+ Animator anim = mNodes.get(i).mAnimation;
+ if (mNodes.get(i).mTotalDuration != anim.getTotalDuration()) {
+ durationChanged = true;
+ break;
+ }
+ }
+ if (!durationChanged) {
+ return;
+ }
+ }
+
+ mDependencyDirty = false;
+ // Traverse all the siblings and make sure they have all the parents
+ int size = mNodes.size();
+ for (int i = 0; i < size; i++) {
+ mNodes.get(i).mParentsAdded = false;
+ }
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node.mParentsAdded) {
+ continue;
+ }
+
+ node.mParentsAdded = true;
+ if (node.mSiblings == null) {
+ continue;
+ }
+
+ // Find all the siblings
+ findSiblings(node, node.mSiblings);
+ node.mSiblings.remove(node);
+
+ // Get parents from all siblings
+ int siblingSize = node.mSiblings.size();
+ for (int j = 0; j < siblingSize; j++) {
+ node.addParents(node.mSiblings.get(j).mParents);
+ }
+
+ // Now make sure all siblings share the same set of parents
+ for (int j = 0; j < siblingSize; j++) {
+ Node sibling = node.mSiblings.get(j);
+ sibling.addParents(node.mParents);
+ sibling.mParentsAdded = true;
+ }
+ }
+
+ for (int i = 0; i < size; i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode && node.mParents == null) {
+ node.addParent(mRootNode);
+ }
+ }
+
+ // Do a DFS on the tree
+ ArrayList<Node> visited = new ArrayList<Node>(mNodes.size());
+ // Assign start/end time
+ mRootNode.mStartTime = 0;
+ mRootNode.mEndTime = mDelayAnim.getDuration();
+ updatePlayTime(mRootNode, visited);
+
+ sortAnimationEvents();
+ mTotalDuration = mEvents.get(mEvents.size() - 1).getTime();
+ }
+
+ private void sortAnimationEvents() {
+ // Sort the list of events in ascending order of their time
+ // Create the list including the delay animation.
+ mEvents.clear();
+ for (int i = 1; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_START));
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_DELAY_ENDED));
+ mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_END));
+ }
+ mEvents.sort(new Comparator<AnimationEvent>() {
+ @Override
+ public int compare(AnimationEvent e1, AnimationEvent e2) {
+ long t1 = e1.getTime();
+ long t2 = e2.getTime();
+ if (t1 == t2) {
+ // For events that happen at the same time, we need them to be in the sequence
+ // (end, start, start delay ended)
+ if (e2.mEvent + e1.mEvent == AnimationEvent.ANIMATION_START
+ + AnimationEvent.ANIMATION_DELAY_ENDED) {
+ // Ensure start delay happens after start
+ return e1.mEvent - e2.mEvent;
+ } else {
+ return e2.mEvent - e1.mEvent;
+ }
+ }
+ if (t2 == DURATION_INFINITE) {
+ return -1;
+ }
+ if (t1 == DURATION_INFINITE) {
+ return 1;
+ }
+ // When neither event happens at INFINITE time:
+ return (int) (t1 - t2);
+ }
+ });
+
+ int eventSize = mEvents.size();
+ // For the same animation, start event has to happen before end.
+ for (int i = 0; i < eventSize;) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ boolean needToSwapStart;
+ if (event.mNode.mStartTime == event.mNode.mEndTime) {
+ needToSwapStart = true;
+ } else if (event.mNode.mEndTime == event.mNode.mStartTime
+ + event.mNode.mAnimation.getStartDelay()) {
+ // Swapping start delay
+ needToSwapStart = false;
+ } else {
+ i++;
+ continue;
+ }
+
+ int startEventId = eventSize;
+ int startDelayEndId = eventSize;
+ for (int j = i + 1; j < eventSize; j++) {
+ if (startEventId < eventSize && startDelayEndId < eventSize) {
+ break;
+ }
+ if (mEvents.get(j).mNode == event.mNode) {
+ if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_START) {
+ // Found start event
+ startEventId = j;
+ } else if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ startDelayEndId = j;
+ }
+ }
+
+ }
+ if (needToSwapStart && startEventId == mEvents.size()) {
+ throw new UnsupportedOperationException("Something went wrong, no start is"
+ + "found after stop for an animation that has the same start and end"
+ + "time.");
+
+ }
+ if (startDelayEndId == mEvents.size()) {
+ throw new UnsupportedOperationException("Something went wrong, no start"
+ + "delay end is found after stop for an animation");
+
+ }
+
+ // We need to make sure start is inserted before start delay ended event,
+ // because otherwise inserting start delay ended events first would change
+ // the start event index.
+ if (needToSwapStart) {
+ AnimationEvent startEvent = mEvents.remove(startEventId);
+ mEvents.add(i, startEvent);
+ i++;
+ }
+
+ AnimationEvent startDelayEndEvent = mEvents.remove(startDelayEndId);
+ mEvents.add(i, startDelayEndEvent);
+ i += 2;
+ } else {
+ i++;
+ }
+ }
+
+ if (!mEvents.isEmpty() && mEvents.get(0).mEvent != AnimationEvent.ANIMATION_START) {
+ throw new UnsupportedOperationException(
+ "Sorting went bad, the start event should always be at index 0");
+ }
+
+ // Add AnimatorSet's start delay node to the beginning
+ mEvents.add(0, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_START));
+ mEvents.add(1, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_DELAY_ENDED));
+ mEvents.add(2, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_END));
+
+ if (mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_START
+ || mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ throw new UnsupportedOperationException(
+ "Something went wrong, the last event is not an end event");
+ }
+ }
+
+ /**
+ * Based on parent's start/end time, calculate children's start/end time. If cycle exists in
+ * the graph, all the nodes on the cycle will be marked to start at {@link #DURATION_INFINITE},
+ * meaning they will ever play.
+ */
+ private void updatePlayTime(Node parent, ArrayList<Node> visited) {
+ if (parent.mChildNodes == null) {
+ if (parent == mRootNode) {
+ // All the animators are in a cycle
+ for (int i = 0; i < mNodes.size(); i++) {
+ Node node = mNodes.get(i);
+ if (node != mRootNode) {
+ node.mStartTime = DURATION_INFINITE;
+ node.mEndTime = DURATION_INFINITE;
+ }
+ }
+ }
+ return;
+ }
+
+ visited.add(parent);
+ int childrenSize = parent.mChildNodes.size();
+ for (int i = 0; i < childrenSize; i++) {
+ Node child = parent.mChildNodes.get(i);
+ child.mTotalDuration = child.mAnimation.getTotalDuration(); // Update cached duration.
+
+ int index = visited.indexOf(child);
+ if (index >= 0) {
+ // Child has been visited, cycle found. Mark all the nodes in the cycle.
+ for (int j = index; j < visited.size(); j++) {
+ visited.get(j).mLatestParent = null;
+ visited.get(j).mStartTime = DURATION_INFINITE;
+ visited.get(j).mEndTime = DURATION_INFINITE;
+ }
+ child.mStartTime = DURATION_INFINITE;
+ child.mEndTime = DURATION_INFINITE;
+ child.mLatestParent = null;
+ Log.w(TAG, "Cycle found in AnimatorSet: " + this);
+ continue;
+ }
+
+ if (child.mStartTime != DURATION_INFINITE) {
+ if (parent.mEndTime == DURATION_INFINITE) {
+ child.mLatestParent = parent;
+ child.mStartTime = DURATION_INFINITE;
+ child.mEndTime = DURATION_INFINITE;
+ } else {
+ if (parent.mEndTime >= child.mStartTime) {
+ child.mLatestParent = parent;
+ child.mStartTime = parent.mEndTime;
+ }
+
+ child.mEndTime = child.mTotalDuration == DURATION_INFINITE
+ ? DURATION_INFINITE : child.mStartTime + child.mTotalDuration;
+ }
+ }
+ updatePlayTime(child, visited);
+ }
+ visited.remove(parent);
+ }
+
+ // Recursively find all the siblings
+ private void findSiblings(Node node, ArrayList<Node> siblings) {
+ if (!siblings.contains(node)) {
+ siblings.add(node);
+ if (node.mSiblings == null) {
+ return;
+ }
+ for (int i = 0; i < node.mSiblings.size(); i++) {
+ findSiblings(node.mSiblings.get(i), siblings);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order
+ * if defined (i.e. sequential or together), then we can use the flag instead of calculating
+ * dynamically. Note that when AnimatorSet is empty this method returns true.
+ * @return whether all the animators in the set are supposed to play together
+ */
+ public boolean shouldPlayTogether() {
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ // All the child nodes are set out to play right after the delay animation
+ return mRootNode.mChildNodes == null || mRootNode.mChildNodes.size() == mNodes.size() - 1;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ return mTotalDuration;
+ }
+
+ private Node getNodeForAnimation(Animator anim) {
+ Node node = mNodeMap.get(anim);
+ if (node == null) {
+ node = new Node(anim);
+ mNodeMap.put(anim, node);
+ mNodes.add(node);
+ }
+ return node;
+ }
+
+ /**
+ * A Node is an embodiment of both the Animator that it wraps as well as
+ * any dependencies that are associated with that Animation. This includes
+ * both dependencies upon other nodes (in the dependencies list) as
+ * well as dependencies of other nodes upon this (in the nodeDependents list).
+ */
+ private static class Node implements Cloneable {
+ Animator mAnimation;
+
+ /**
+ * Child nodes are the nodes associated with animations that will be played immediately
+ * after current node.
+ */
+ ArrayList<Node> mChildNodes = null;
+
+ /**
+ * Flag indicating whether the animation in this node is finished. This flag
+ * is used by AnimatorSet to check, as each animation ends, whether all child animations
+ * are mEnded and it's time to send out an end event for the entire AnimatorSet.
+ */
+ boolean mEnded = false;
+
+ /**
+ * Nodes with animations that are defined to play simultaneously with the animation
+ * associated with this current node.
+ */
+ ArrayList<Node> mSiblings;
+
+ /**
+ * Parent nodes are the nodes with animations preceding current node's animation. Parent
+ * nodes here are derived from user defined animation sequence.
+ */
+ ArrayList<Node> mParents;
+
+ /**
+ * Latest parent is the parent node associated with a animation that finishes after all
+ * the other parents' animations.
+ */
+ Node mLatestParent = null;
+
+ boolean mParentsAdded = false;
+ long mStartTime = 0;
+ long mEndTime = 0;
+ long mTotalDuration = 0;
+
+ /**
+ * Constructs the Node with the animation that it encapsulates. A Node has no
+ * dependencies by default; dependencies are added via the addDependency()
+ * method.
+ *
+ * @param animation The animation that the Node encapsulates.
+ */
+ public Node(Animator animation) {
+ this.mAnimation = animation;
+ }
+
+ @Override
+ public Node clone() {
+ try {
+ Node node = (Node) super.clone();
+ node.mAnimation = mAnimation.clone();
+ if (mChildNodes != null) {
+ node.mChildNodes = new ArrayList<>(mChildNodes);
+ }
+ if (mSiblings != null) {
+ node.mSiblings = new ArrayList<>(mSiblings);
+ }
+ if (mParents != null) {
+ node.mParents = new ArrayList<>(mParents);
+ }
+ node.mEnded = false;
+ return node;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ void addChild(Node node) {
+ if (mChildNodes == null) {
+ mChildNodes = new ArrayList<>();
+ }
+ if (!mChildNodes.contains(node)) {
+ mChildNodes.add(node);
+ node.addParent(this);
+ }
+ }
+
+ public void addSibling(Node node) {
+ if (mSiblings == null) {
+ mSiblings = new ArrayList<Node>();
+ }
+ if (!mSiblings.contains(node)) {
+ mSiblings.add(node);
+ node.addSibling(this);
+ }
+ }
+
+ public void addParent(Node node) {
+ if (mParents == null) {
+ mParents = new ArrayList<Node>();
+ }
+ if (!mParents.contains(node)) {
+ mParents.add(node);
+ node.addChild(this);
+ }
+ }
+
+ public void addParents(ArrayList<Node> parents) {
+ if (parents == null) {
+ return;
+ }
+ int size = parents.size();
+ for (int i = 0; i < size; i++) {
+ addParent(parents.get(i));
+ }
+ }
+ }
+
+ /**
+ * This class is a wrapper around a node and an event for the animation corresponding to the
+ * node. The 3 types of events represent the start of an animation, the end of a start delay of
+ * an animation, and the end of an animation. When playing forward (i.e. in the non-reverse
+ * direction), start event marks when start() should be called, and end event corresponds to
+ * when the animation should finish. When playing in reverse, start delay will not be a part
+ * of the animation. Therefore, reverse() is called at the end event, and animation should end
+ * at the delay ended event.
+ */
+ private static class AnimationEvent {
+ static final int ANIMATION_START = 0;
+ static final int ANIMATION_DELAY_ENDED = 1;
+ static final int ANIMATION_END = 2;
+ final Node mNode;
+ final int mEvent;
+
+ AnimationEvent(Node node, int event) {
+ mNode = node;
+ mEvent = event;
+ }
+
+ long getTime() {
+ if (mEvent == ANIMATION_START) {
+ return mNode.mStartTime;
+ } else if (mEvent == ANIMATION_DELAY_ENDED) {
+ return mNode.mStartTime == DURATION_INFINITE
+ ? DURATION_INFINITE : mNode.mStartTime + mNode.mAnimation.getStartDelay();
+ } else {
+ return mNode.mEndTime;
+ }
+ }
+
+ public String toString() {
+ String eventStr = mEvent == ANIMATION_START ? "start" : (
+ mEvent == ANIMATION_DELAY_ENDED ? "delay ended" : "end");
+ return eventStr + " " + mNode.mAnimation.toString();
+ }
+ }
+
+ private class SeekState {
+ private long mPlayTime = -1;
+ private boolean mSeekingInReverse = false;
+ void reset() {
+ mPlayTime = -1;
+ mSeekingInReverse = false;
+ }
+
+ void setPlayTime(long playTime, boolean inReverse) {
+ // Clamp the play time
+ if (getTotalDuration() != DURATION_INFINITE) {
+ mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+ } else {
+ mPlayTime = playTime;
+ }
+ mPlayTime = Math.max(0, mPlayTime);
+ mSeekingInReverse = inReverse;
+ }
+
+ void updateSeekDirection(boolean inReverse) {
+ // Change seek direction without changing the overall fraction
+ if (inReverse && getTotalDuration() == DURATION_INFINITE) {
+ throw new UnsupportedOperationException("Error: Cannot reverse infinite animator"
+ + " set");
+ }
+ if (mPlayTime >= 0) {
+ if (inReverse != mSeekingInReverse) {
+ mPlayTime = getTotalDuration() - mStartDelay - mPlayTime;
+ mSeekingInReverse = inReverse;
+ }
+ }
+ }
+
+ long getPlayTime() {
+ return mPlayTime;
+ }
+
+ /**
+ * Returns the playtime assuming the animation is forward playing
+ */
+ long getPlayTimeNormalized() {
+ if (mReversing) {
+ return getTotalDuration() - mStartDelay - mPlayTime;
+ }
+ return mPlayTime;
+ }
+
+ boolean isActive() {
+ return mPlayTime != -1;
+ }
+ }
+
+ /**
+ * The <code>Builder</code> object is a utility class to facilitate adding animations to a
+ * <code>AnimatorSet</code> along with the relationships between the various animations. The
+ * intention of the <code>Builder</code> methods, along with the {@link
+ * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible
+ * to express the dependency relationships of animations in a natural way. Developers can also
+ * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link
+ * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need,
+ * but it might be easier in some situations to express the AnimatorSet of animations in pairs.
+ * <p/>
+ * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
+ * internally via a call to {@link AnimatorSet#play(Animator)}.</p>
+ * <p/>
+ * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to
+ * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
+ * <pre>
+ * AnimatorSet s = new AnimatorSet();
+ * s.play(anim1).with(anim2);
+ * s.play(anim2).before(anim3);
+ * s.play(anim4).after(anim3);
+ * </pre>
+ * <p/>
+ * <p>Note in the example that both {@link Builder#before(Animator)} and {@link
+ * Builder#after(Animator)} are used. These are just different ways of expressing the same
+ * relationship and are provided to make it easier to say things in a way that is more natural,
+ * depending on the situation.</p>
+ * <p/>
+ * <p>It is possible to make several calls into the same <code>Builder</code> object to express
+ * multiple relationships. However, note that it is only the animation passed into the initial
+ * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive
+ * calls to the <code>Builder</code> object. For example, the following code starts both anim2
+ * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
+ * anim3:
+ * <pre>
+ * AnimatorSet s = new AnimatorSet();
+ * s.play(anim1).before(anim2).before(anim3);
+ * </pre>
+ * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
+ * relationship correctly:</p>
+ * <pre>
+ * AnimatorSet s = new AnimatorSet();
+ * s.play(anim1).before(anim2);
+ * s.play(anim2).before(anim3);
+ * </pre>
+ * <p/>
+ * <p>Note that it is possible to express relationships that cannot be resolved and will not
+ * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no
+ * sense. In general, circular dependencies like this one (or more indirect ones where a depends
+ * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets
+ * that can boil down to a simple, one-way relationship of animations starting with, before, and
+ * after other, different, animations.</p>
+ */
+ public class Builder {
+
+ /**
+ * This tracks the current node being processed. It is supplied to the play() method
+ * of AnimatorSet and passed into the constructor of Builder.
+ */
+ private Node mCurrentNode;
+
+ /**
+ * package-private constructor. Builders are only constructed by AnimatorSet, when the
+ * play() method is called.
+ *
+ * @param anim The animation that is the dependency for the other animations passed into
+ * the other methods of this Builder object.
+ */
+ Builder(Animator anim) {
+ mDependencyDirty = true;
+ mCurrentNode = getNodeForAnimation(anim);
+ }
+
+ /**
+ * Sets up the given animation to play at the same time as the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
+ *
+ * @param anim The animation that will play when the animation supplied to the
+ * {@link AnimatorSet#play(Animator)} method starts.
+ */
+ public Builder with(Animator anim) {
+ Node node = getNodeForAnimation(anim);
+ mCurrentNode.addSibling(node);
+ return this;
+ }
+
+ /**
+ * Sets up the given animation to play when the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+ * ends.
+ *
+ * @param anim The animation that will play when the animation supplied to the
+ * {@link AnimatorSet#play(Animator)} method ends.
+ */
+ public Builder before(Animator anim) {
+ Node node = getNodeForAnimation(anim);
+ mCurrentNode.addChild(node);
+ return this;
+ }
+
+ /**
+ * Sets up the given animation to play when the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+ * to start when the animation supplied in this method call ends.
+ *
+ * @param anim The animation whose end will cause the animation supplied to the
+ * {@link AnimatorSet#play(Animator)} method to play.
+ */
+ public Builder after(Animator anim) {
+ Node node = getNodeForAnimation(anim);
+ mCurrentNode.addParent(node);
+ return this;
+ }
+
+ /**
+ * Sets up the animation supplied in the
+ * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+ * to play when the given amount of time elapses.
+ *
+ * @param delay The number of milliseconds that should elapse before the
+ * animation starts.
+ */
+ public Builder after(long delay) {
+ // setup a ValueAnimator just to run the clock
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setDuration(delay);
+ after(anim);
+ return this;
+ }
+
+ }
+
+}
diff --git a/android-34/android/animation/ArgbEvaluator.java b/android-34/android/animation/ArgbEvaluator.java
new file mode 100644
index 0000000..9519ddd
--- /dev/null
+++ b/android-34/android/animation/ArgbEvaluator.java
@@ -0,0 +1,93 @@
+/*
+ * 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.animation;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * This evaluator can be used to perform type interpolation between integer
+ * values that represent ARGB colors.
+ */
+public class ArgbEvaluator implements TypeEvaluator {
+ private static final ArgbEvaluator sInstance = new ArgbEvaluator();
+
+ /**
+ * Returns an instance of <code>ArgbEvaluator</code> that may be used in
+ * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
+ * be used in multiple <code>Animator</code>s because it holds no state.
+ * @return An instance of <code>ArgbEvalutor</code>.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static ArgbEvaluator getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * This function returns the calculated in-between value for a color
+ * given integers that represent the start and end values in the four
+ * bytes of the 32-bit int. Each channel is separately linearly interpolated
+ * and the resulting calculated values are recombined into the return value.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue A 32-bit int value representing colors in the
+ * separate bytes of the parameter
+ * @param endValue A 32-bit int value representing colors in the
+ * separate bytes of the parameter
+ * @return A value that is calculated to be the linearly interpolated
+ * result, derived by separating the start and end values into separate
+ * color channels and interpolating each one separately, recombining the
+ * resulting values in the same way.
+ */
+ public Object evaluate(float fraction, Object startValue, Object endValue) {
+ int startInt = (Integer) startValue;
+ float startA = ((startInt >> 24) & 0xff) / 255.0f;
+ float startR = ((startInt >> 16) & 0xff) / 255.0f;
+ float startG = ((startInt >> 8) & 0xff) / 255.0f;
+ float startB = ( startInt & 0xff) / 255.0f;
+
+ int endInt = (Integer) endValue;
+ float endA = ((endInt >> 24) & 0xff) / 255.0f;
+ float endR = ((endInt >> 16) & 0xff) / 255.0f;
+ float endG = ((endInt >> 8) & 0xff) / 255.0f;
+ float endB = ( endInt & 0xff) / 255.0f;
+
+ // convert from sRGB to linear
+ startR = (float) Math.pow(startR, 2.2);
+ startG = (float) Math.pow(startG, 2.2);
+ startB = (float) Math.pow(startB, 2.2);
+
+ endR = (float) Math.pow(endR, 2.2);
+ endG = (float) Math.pow(endG, 2.2);
+ endB = (float) Math.pow(endB, 2.2);
+
+ // compute the interpolated color in linear space
+ float a = startA + fraction * (endA - startA);
+ float r = startR + fraction * (endR - startR);
+ float g = startG + fraction * (endG - startG);
+ float b = startB + fraction * (endB - startB);
+
+ // convert back to sRGB in the [0..255] range
+ a = a * 255.0f;
+ r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
+ g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
+ b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
+
+ return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
+ }
+}
\ No newline at end of file
diff --git a/android-34/android/animation/BidirectionalTypeConverter.java b/android-34/android/animation/BidirectionalTypeConverter.java
new file mode 100644
index 0000000..960650e
--- /dev/null
+++ b/android-34/android/animation/BidirectionalTypeConverter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.animation;
+
+/**
+ * Abstract base class used convert type T to another type V and back again. This
+ * is necessary when the value types of in animation are different from the property
+ * type. BidirectionalTypeConverter is needed when only the final value for the
+ * animation is supplied to animators.
+ * @see PropertyValuesHolder#setConverter(TypeConverter)
+ */
+public abstract class BidirectionalTypeConverter<T, V> extends TypeConverter<T, V> {
+ private BidirectionalTypeConverter mInvertedConverter;
+
+ public BidirectionalTypeConverter(Class<T> fromClass, Class<V> toClass) {
+ super(fromClass, toClass);
+ }
+
+ /**
+ * Does a conversion from the target type back to the source type. The subclass
+ * must implement this when a TypeConverter is used in animations and current
+ * values will need to be read for an animation.
+ * @param value The Object to convert.
+ * @return A value of type T, converted from <code>value</code>.
+ */
+ public abstract T convertBack(V value);
+
+ /**
+ * Returns the inverse of this converter, where the from and to classes are reversed.
+ * The inverted converter uses this convert to call {@link #convertBack(Object)} for
+ * {@link #convert(Object)} calls and {@link #convert(Object)} for
+ * {@link #convertBack(Object)} calls.
+ * @return The inverse of this converter, where the from and to classes are reversed.
+ */
+ public BidirectionalTypeConverter<V, T> invert() {
+ if (mInvertedConverter == null) {
+ mInvertedConverter = new InvertedConverter(this);
+ }
+ return mInvertedConverter;
+ }
+
+ private static class InvertedConverter<From, To> extends BidirectionalTypeConverter<From, To> {
+ private BidirectionalTypeConverter<To, From> mConverter;
+
+ public InvertedConverter(BidirectionalTypeConverter<To, From> converter) {
+ super(converter.getTargetType(), converter.getSourceType());
+ mConverter = converter;
+ }
+
+ @Override
+ public From convertBack(To value) {
+ return mConverter.convert(value);
+ }
+
+ @Override
+ public To convert(From value) {
+ return mConverter.convertBack(value);
+ }
+ }
+}
diff --git a/android-34/android/animation/FloatArrayEvaluator.java b/android-34/android/animation/FloatArrayEvaluator.java
new file mode 100644
index 0000000..9ae1197
--- /dev/null
+++ b/android-34/android/animation/FloatArrayEvaluator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+public class FloatArrayEvaluator implements TypeEvaluator<float[]> {
+
+ private float[] mArray;
+
+ /**
+ * Create a FloatArrayEvaluator that does not reuse the animated value. Care must be taken
+ * when using this option because on every evaluation a new <code>float[]</code> will be
+ * allocated.
+ *
+ * @see #FloatArrayEvaluator(float[])
+ */
+ public FloatArrayEvaluator() {
+ }
+
+ /**
+ * Create a FloatArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param reuseArray The array to modify and return from <code>evaluate</code>.
+ */
+ public FloatArrayEvaluator(float[] reuseArray) {
+ mArray = reuseArray;
+ }
+
+ /**
+ * Interpolates the value at each index by the fraction. If
+ * {@link #FloatArrayEvaluator(float[])} was used to construct this object,
+ * <code>reuseArray</code> will be returned, otherwise a new <code>float[]</code>
+ * will be returned.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return A <code>float[]</code> where each element is an interpolation between
+ * the same index in startValue and endValue.
+ */
+ @Override
+ public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
+ float[] array = mArray;
+ if (array == null) {
+ array = new float[startValue.length];
+ }
+
+ for (int i = 0; i < array.length; i++) {
+ float start = startValue[i];
+ float end = endValue[i];
+ array[i] = start + (fraction * (end - start));
+ }
+ return array;
+ }
+}
diff --git a/android-34/android/animation/FloatEvaluator.java b/android-34/android/animation/FloatEvaluator.java
new file mode 100644
index 0000000..ae90e37
--- /dev/null
+++ b/android-34/android/animation/FloatEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float</code> values.
+ */
+public class FloatEvaluator implements TypeEvaluator<Number> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>float</code> or
+ * <code>Float</code>
+ * @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Float evaluate(float fraction, Number startValue, Number endValue) {
+ float startFloat = startValue.floatValue();
+ return startFloat + fraction * (endValue.floatValue() - startFloat);
+ }
+}
\ No newline at end of file
diff --git a/android-34/android/animation/FloatKeyframeSet.java b/android-34/android/animation/FloatKeyframeSet.java
new file mode 100644
index 0000000..11837b5
--- /dev/null
+++ b/android-34/android/animation/FloatKeyframeSet.java
@@ -0,0 +1,119 @@
+/*
+ * 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.animation;
+
+import android.animation.Keyframe.FloatKeyframe;
+
+import java.util.List;
+
+/**
+ * This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ *
+ * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for
+ * int, exists to speed up the getValue() method when there is no custom
+ * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
+ * Object equivalents of these primitive types.</p>
+ */
+class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
+ public FloatKeyframeSet(FloatKeyframe... keyframes) {
+ super(keyframes);
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getFloatValue(fraction);
+ }
+
+ @Override
+ public FloatKeyframeSet clone() {
+ final List<Keyframe> keyframes = mKeyframes;
+ final int numKeyframes = mKeyframes.size();
+ FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
+ }
+ FloatKeyframeSet newSet = new FloatKeyframeSet(newKeyframes);
+ return newSet;
+ }
+
+ @Override
+ public float getFloatValue(float fraction) {
+ if (fraction <= 0f) {
+ final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
+ final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
+ float prevValue = prevKeyframe.getFloatValue();
+ float nextValue = nextKeyframe.getFloatValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + intervalFraction * (nextValue - prevValue) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ floatValue();
+ } else if (fraction >= 1f) {
+ final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
+ final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
+ float prevValue = prevKeyframe.getFloatValue();
+ float nextValue = nextKeyframe.getFloatValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + intervalFraction * (nextValue - prevValue) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ floatValue();
+ }
+ FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
+ for (int i = 1; i < mNumKeyframes; ++i) {
+ FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
+ if (fraction < nextKeyframe.getFraction()) {
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+ (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+ float prevValue = prevKeyframe.getFloatValue();
+ float nextValue = nextKeyframe.getFloatValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
+ return mEvaluator == null ?
+ prevValue + intervalFraction * (nextValue - prevValue) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ floatValue();
+ }
+ prevKeyframe = nextKeyframe;
+ }
+ // shouldn't get here
+ return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
+ }
+
+ @Override
+ public Class getType() {
+ return Float.class;
+ }
+}
+
diff --git a/android-34/android/animation/IntArrayEvaluator.java b/android-34/android/animation/IntArrayEvaluator.java
new file mode 100644
index 0000000..d7f10f3
--- /dev/null
+++ b/android-34/android/animation/IntArrayEvaluator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+public class IntArrayEvaluator implements TypeEvaluator<int[]> {
+
+ private int[] mArray;
+
+ /**
+ * Create an IntArrayEvaluator that does not reuse the animated value. Care must be taken
+ * when using this option because on every evaluation a new <code>int[]</code> will be
+ * allocated.
+ *
+ * @see #IntArrayEvaluator(int[])
+ */
+ public IntArrayEvaluator() {
+ }
+
+ /**
+ * Create an IntArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param reuseArray The array to modify and return from <code>evaluate</code>.
+ */
+ public IntArrayEvaluator(int[] reuseArray) {
+ mArray = reuseArray;
+ }
+
+ /**
+ * Interpolates the value at each index by the fraction. If {@link #IntArrayEvaluator(int[])}
+ * was used to construct this object, <code>reuseArray</code> will be returned, otherwise
+ * a new <code>int[]</code> will be returned.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return An <code>int[]</code> where each element is an interpolation between
+ * the same index in startValue and endValue.
+ */
+ @Override
+ public int[] evaluate(float fraction, int[] startValue, int[] endValue) {
+ int[] array = mArray;
+ if (array == null) {
+ array = new int[startValue.length];
+ }
+ for (int i = 0; i < array.length; i++) {
+ int start = startValue[i];
+ int end = endValue[i];
+ array[i] = (int) (start + (fraction * (end - start)));
+ }
+ return array;
+ }
+}
diff --git a/android-34/android/animation/IntEvaluator.java b/android-34/android/animation/IntEvaluator.java
new file mode 100644
index 0000000..1de2ae7
--- /dev/null
+++ b/android-34/android/animation/IntEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int</code> values.
+ */
+public class IntEvaluator implements TypeEvaluator<Integer> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value; should be of type <code>int</code> or
+ * <code>Integer</code>
+ * @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ int startInt = startValue;
+ return (int)(startInt + fraction * (endValue - startInt));
+ }
+}
diff --git a/android-34/android/animation/IntKeyframeSet.java b/android-34/android/animation/IntKeyframeSet.java
new file mode 100644
index 0000000..f1e146e
--- /dev/null
+++ b/android-34/android/animation/IntKeyframeSet.java
@@ -0,0 +1,118 @@
+/*
+ * 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.animation;
+
+import android.animation.Keyframe.IntKeyframe;
+
+import java.util.List;
+
+/**
+ * This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ *
+ * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for
+ * float, exists to speed up the getValue() method when there is no custom
+ * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
+ * Object equivalents of these primitive types.</p>
+ */
+class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
+ public IntKeyframeSet(IntKeyframe... keyframes) {
+ super(keyframes);
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getIntValue(fraction);
+ }
+
+ @Override
+ public IntKeyframeSet clone() {
+ List<Keyframe> keyframes = mKeyframes;
+ int numKeyframes = mKeyframes.size();
+ IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone();
+ }
+ IntKeyframeSet newSet = new IntKeyframeSet(newKeyframes);
+ return newSet;
+ }
+
+ @Override
+ public int getIntValue(float fraction) {
+ if (fraction <= 0f) {
+ final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
+ final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
+ int prevValue = prevKeyframe.getIntValue();
+ int nextValue = nextKeyframe.getIntValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ intValue();
+ } else if (fraction >= 1f) {
+ final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
+ final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
+ int prevValue = prevKeyframe.getIntValue();
+ int nextValue = nextKeyframe.getIntValue();
+ float prevFraction = prevKeyframe.getFraction();
+ float nextFraction = nextKeyframe.getFraction();
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+ return mEvaluator == null ?
+ prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
+ }
+ IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
+ for (int i = 1; i < mNumKeyframes; ++i) {
+ IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
+ if (fraction < nextKeyframe.getFraction()) {
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+ (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+ int prevValue = prevKeyframe.getIntValue();
+ int nextValue = nextKeyframe.getIntValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
+ return mEvaluator == null ?
+ prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+ ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+ intValue();
+ }
+ prevKeyframe = nextKeyframe;
+ }
+ // shouldn't get here
+ return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
+ }
+
+ @Override
+ public Class getType() {
+ return Integer.class;
+ }
+}
+
diff --git a/android-34/android/animation/Keyframe.java b/android-34/android/animation/Keyframe.java
new file mode 100644
index 0000000..bcb94d1
--- /dev/null
+++ b/android-34/android/animation/Keyframe.java
@@ -0,0 +1,388 @@
+/*
+ * 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.animation;
+
+/**
+ * This class holds a time/value pair for an animation. The Keyframe class is used
+ * by {@link ValueAnimator} to define the values that the animation target will have over the course
+ * of the animation. As the time proceeds from one keyframe to the other, the value of the
+ * target object will animate between the value at the previous keyframe and the value at the
+ * next keyframe. Each keyframe also holds an optional {@link TimeInterpolator}
+ * object, which defines the time interpolation over the intervalue preceding the keyframe.
+ *
+ * <p>The Keyframe class itself is abstract. The type-specific factory methods will return
+ * a subclass of Keyframe specific to the type of value being stored. This is done to improve
+ * performance when dealing with the most common cases (e.g., <code>float</code> and
+ * <code>int</code> values). Other types will fall into a more general Keyframe class that
+ * treats its values as Objects. Unless your animation requires dealing with a custom type
+ * or a data structure that needs to be animated directly (and evaluated using an implementation
+ * of {@link TypeEvaluator}), you should stick to using float and int as animations using those
+ * types have lower runtime overhead than other types.</p>
+ */
+public abstract class Keyframe implements Cloneable {
+ /**
+ * Flag to indicate whether this keyframe has a valid value. This flag is used when an
+ * animation first starts, to populate placeholder keyframes with real values derived
+ * from the target object.
+ */
+ boolean mHasValue;
+
+ /**
+ * Flag to indicate whether the value in the keyframe was read from the target object or not.
+ * If so, its value will be recalculated if target changes.
+ */
+ boolean mValueWasSetOnStart;
+
+
+ /**
+ * The time at which mValue will hold true.
+ */
+ float mFraction;
+
+ /**
+ * The type of the value in this Keyframe. This type is determined at construction time,
+ * based on the type of the <code>value</code> object passed into the constructor.
+ */
+ Class mValueType;
+
+ /**
+ * The optional time interpolator for the interval preceding this keyframe. A null interpolator
+ * (the default) results in linear interpolation over the interval.
+ */
+ private TimeInterpolator mInterpolator = null;
+
+
+
+ /**
+ * Constructs a Keyframe object with the given time and value. The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ * @param value The value that the object will animate to as the animation time approaches
+ * the time in this keyframe, and the value animated from as the time passes the time in
+ * this keyframe.
+ */
+ public static Keyframe ofInt(float fraction, int value) {
+ return new IntKeyframe(fraction, value);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time. The value at this time will be derived
+ * from the target object when the animation first starts (note that this implies that keyframes
+ * with no initial value must be used as part of an {@link ObjectAnimator}).
+ * The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ */
+ public static Keyframe ofInt(float fraction) {
+ return new IntKeyframe(fraction);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time and value. The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ * @param value The value that the object will animate to as the animation time approaches
+ * the time in this keyframe, and the value animated from as the time passes the time in
+ * this keyframe.
+ */
+ public static Keyframe ofFloat(float fraction, float value) {
+ return new FloatKeyframe(fraction, value);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time. The value at this time will be derived
+ * from the target object when the animation first starts (note that this implies that keyframes
+ * with no initial value must be used as part of an {@link ObjectAnimator}).
+ * The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ */
+ public static Keyframe ofFloat(float fraction) {
+ return new FloatKeyframe(fraction);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time and value. The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ * @param value The value that the object will animate to as the animation time approaches
+ * the time in this keyframe, and the value animated from as the time passes the time in
+ * this keyframe.
+ */
+ public static Keyframe ofObject(float fraction, Object value) {
+ return new ObjectKeyframe(fraction, value);
+ }
+
+ /**
+ * Constructs a Keyframe object with the given time. The value at this time will be derived
+ * from the target object when the animation first starts (note that this implies that keyframes
+ * with no initial value must be used as part of an {@link ObjectAnimator}).
+ * The time defines the
+ * time, as a proportion of an overall animation's duration, at which the value will hold true
+ * for the animation. The value for the animation between keyframes will be calculated as
+ * an interpolation between the values at those keyframes.
+ *
+ * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+ * of time elapsed of the overall animation duration.
+ */
+ public static Keyframe ofObject(float fraction) {
+ return new ObjectKeyframe(fraction, null);
+ }
+
+ /**
+ * Indicates whether this keyframe has a valid value. This method is called internally when
+ * an {@link ObjectAnimator} first starts; keyframes without values are assigned values at
+ * that time by deriving the value for the property from the target object.
+ *
+ * @return boolean Whether this object has a value assigned.
+ */
+ public boolean hasValue() {
+ return mHasValue;
+ }
+
+ /**
+ * If the Keyframe's value was acquired from the target object, this flag should be set so that,
+ * if target changes, value will be reset.
+ *
+ * @return boolean Whether this Keyframe's value was retieved from the target object or not.
+ */
+ boolean valueWasSetOnStart() {
+ return mValueWasSetOnStart;
+ }
+
+ void setValueWasSetOnStart(boolean valueWasSetOnStart) {
+ mValueWasSetOnStart = valueWasSetOnStart;
+ }
+
+ /**
+ * Gets the value for this Keyframe.
+ *
+ * @return The value for this Keyframe.
+ */
+ public abstract Object getValue();
+
+ /**
+ * Sets the value for this Keyframe.
+ *
+ * @param value value for this Keyframe.
+ */
+ public abstract void setValue(Object value);
+
+ /**
+ * Gets the time for this keyframe, as a fraction of the overall animation duration.
+ *
+ * @return The time associated with this keyframe, as a fraction of the overall animation
+ * duration. This should be a value between 0 and 1.
+ */
+ public float getFraction() {
+ return mFraction;
+ }
+
+ /**
+ * Sets the time for this keyframe, as a fraction of the overall animation duration.
+ *
+ * @param fraction time associated with this keyframe, as a fraction of the overall animation
+ * duration. This should be a value between 0 and 1.
+ */
+ public void setFraction(float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+ * that there is no interpolation, which is the same as linear interpolation.
+ *
+ * @return The optional interpolator for this Keyframe.
+ */
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * Sets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+ * that there is no interpolation, which is the same as linear interpolation.
+ *
+ * @return The optional interpolator for this Keyframe.
+ */
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Gets the type of keyframe. This information is used by ValueAnimator to determine the type of
+ * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based
+ * on the type of Keyframe created.
+ *
+ * @return The type of the value stored in the Keyframe.
+ */
+ public Class getType() {
+ return mValueType;
+ }
+
+ @Override
+ public abstract Keyframe clone();
+
+ /**
+ * This internal subclass is used for all types which are not int or float.
+ */
+ static class ObjectKeyframe extends Keyframe {
+
+ /**
+ * The value of the animation at the time mFraction.
+ */
+ Object mValue;
+
+ ObjectKeyframe(float fraction, Object value) {
+ mFraction = fraction;
+ mValue = value;
+ mHasValue = (value != null);
+ mValueType = mHasValue ? value.getClass() : Object.class;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ mValue = value;
+ mHasValue = (value != null);
+ }
+
+ @Override
+ public ObjectKeyframe clone() {
+ ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), hasValue() ? mValue : null);
+ kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
+ kfClone.setInterpolator(getInterpolator());
+ return kfClone;
+ }
+ }
+
+ /**
+ * Internal subclass used when the keyframe value is of type int.
+ */
+ static class IntKeyframe extends Keyframe {
+
+ /**
+ * The value of the animation at the time mFraction.
+ */
+ int mValue;
+
+ IntKeyframe(float fraction, int value) {
+ mFraction = fraction;
+ mValue = value;
+ mValueType = int.class;
+ mHasValue = true;
+ }
+
+ IntKeyframe(float fraction) {
+ mFraction = fraction;
+ mValueType = int.class;
+ }
+
+ public int getIntValue() {
+ return mValue;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ if (value != null && value.getClass() == Integer.class) {
+ mValue = ((Integer)value).intValue();
+ mHasValue = true;
+ }
+ }
+
+ @Override
+ public IntKeyframe clone() {
+ IntKeyframe kfClone = mHasValue ?
+ new IntKeyframe(getFraction(), mValue) :
+ new IntKeyframe(getFraction());
+ kfClone.setInterpolator(getInterpolator());
+ kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
+ return kfClone;
+ }
+ }
+
+ /**
+ * Internal subclass used when the keyframe value is of type float.
+ */
+ static class FloatKeyframe extends Keyframe {
+ /**
+ * The value of the animation at the time mFraction.
+ */
+ float mValue;
+
+ FloatKeyframe(float fraction, float value) {
+ mFraction = fraction;
+ mValue = value;
+ mValueType = float.class;
+ mHasValue = true;
+ }
+
+ FloatKeyframe(float fraction) {
+ mFraction = fraction;
+ mValueType = float.class;
+ }
+
+ public float getFloatValue() {
+ return mValue;
+ }
+
+ public Object getValue() {
+ return mValue;
+ }
+
+ public void setValue(Object value) {
+ if (value != null && value.getClass() == Float.class) {
+ mValue = ((Float)value).floatValue();
+ mHasValue = true;
+ }
+ }
+
+ @Override
+ public FloatKeyframe clone() {
+ FloatKeyframe kfClone = mHasValue ?
+ new FloatKeyframe(getFraction(), mValue) :
+ new FloatKeyframe(getFraction());
+ kfClone.setInterpolator(getInterpolator());
+ kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
+ return kfClone;
+ }
+ }
+}
diff --git a/android-34/android/animation/KeyframeSet.java b/android-34/android/animation/KeyframeSet.java
new file mode 100644
index 0000000..116d063
--- /dev/null
+++ b/android-34/android/animation/KeyframeSet.java
@@ -0,0 +1,257 @@
+/*
+ * 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.animation;
+
+import android.animation.Keyframe.FloatKeyframe;
+import android.animation.Keyframe.IntKeyframe;
+import android.animation.Keyframe.ObjectKeyframe;
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ * @hide
+ */
+public class KeyframeSet implements Keyframes {
+
+ int mNumKeyframes;
+
+ Keyframe mFirstKeyframe;
+ Keyframe mLastKeyframe;
+ TimeInterpolator mInterpolator; // only used in the 2-keyframe case
+ List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
+ TypeEvaluator mEvaluator;
+
+
+ public KeyframeSet(Keyframe... keyframes) {
+ mNumKeyframes = keyframes.length;
+ // immutable list
+ mKeyframes = Arrays.asList(keyframes);
+ mFirstKeyframe = keyframes[0];
+ mLastKeyframe = keyframes[mNumKeyframes - 1];
+ mInterpolator = mLastKeyframe.getInterpolator();
+ }
+
+ public List<Keyframe> getKeyframes() {
+ return mKeyframes;
+ }
+
+ public static KeyframeSet ofInt(int... values) {
+ int numKeyframes = values.length;
+ IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
+ keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
+ } else {
+ keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] =
+ (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+ }
+ }
+ return new IntKeyframeSet(keyframes);
+ }
+
+ public static KeyframeSet ofFloat(float... values) {
+ boolean badValue = false;
+ int numKeyframes = values.length;
+ FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
+ keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
+ if (Float.isNaN(values[0])) {
+ badValue = true;
+ }
+ } else {
+ keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] =
+ (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+ if (Float.isNaN(values[i])) {
+ badValue = true;
+ }
+ }
+ }
+ if (badValue) {
+ Log.w("Animator", "Bad value (NaN) in float animator");
+ }
+ return new FloatKeyframeSet(keyframes);
+ }
+
+ public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
+ // if all keyframes of same primitive type, create the appropriate KeyframeSet
+ int numKeyframes = keyframes.length;
+ boolean hasFloat = false;
+ boolean hasInt = false;
+ boolean hasOther = false;
+ for (int i = 0; i < numKeyframes; ++i) {
+ if (keyframes[i] instanceof FloatKeyframe) {
+ hasFloat = true;
+ } else if (keyframes[i] instanceof IntKeyframe) {
+ hasInt = true;
+ } else {
+ hasOther = true;
+ }
+ }
+ if (hasFloat && !hasInt && !hasOther) {
+ FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ floatKeyframes[i] = (FloatKeyframe) keyframes[i];
+ }
+ return new FloatKeyframeSet(floatKeyframes);
+ } else if (hasInt && !hasFloat && !hasOther) {
+ IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ intKeyframes[i] = (IntKeyframe) keyframes[i];
+ }
+ return new IntKeyframeSet(intKeyframes);
+ } else {
+ return new KeyframeSet(keyframes);
+ }
+ }
+
+ public static KeyframeSet ofObject(Object... values) {
+ int numKeyframes = values.length;
+ ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
+ if (numKeyframes == 1) {
+ keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
+ keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
+ } else {
+ keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
+ for (int i = 1; i < numKeyframes; ++i) {
+ keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
+ }
+ }
+ return new KeyframeSet(keyframes);
+ }
+
+ public static PathKeyframes ofPath(Path path) {
+ return new PathKeyframes(path);
+ }
+
+ public static PathKeyframes ofPath(Path path, float error) {
+ return new PathKeyframes(path, error);
+ }
+
+ /**
+ * Sets the TypeEvaluator to be used when calculating animated values. This object
+ * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet,
+ * both of which assume their own evaluator to speed up calculations with those primitive
+ * types.
+ *
+ * @param evaluator The TypeEvaluator to be used to calculate animated values.
+ */
+ public void setEvaluator(TypeEvaluator evaluator) {
+ mEvaluator = evaluator;
+ }
+
+ @Override
+ public Class getType() {
+ return mFirstKeyframe.getType();
+ }
+
+ @Override
+ public KeyframeSet clone() {
+ List<Keyframe> keyframes = mKeyframes;
+ int numKeyframes = mKeyframes.size();
+ final Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+ for (int i = 0; i < numKeyframes; ++i) {
+ newKeyframes[i] = keyframes.get(i).clone();
+ }
+ KeyframeSet newSet = new KeyframeSet(newKeyframes);
+ return newSet;
+ }
+
+ /**
+ * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+ * animation's interpolator) and the evaluator used to calculate in-between values. This
+ * function maps the input fraction to the appropriate keyframe interval and a fraction
+ * between them and returns the interpolated value. Note that the input fraction may fall
+ * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+ * spring interpolation that might send the fraction past 1.0). We handle this situation by
+ * just using the two keyframes at the appropriate end when the value is outside those bounds.
+ *
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ public Object getValue(float fraction) {
+ // Special-case optimization for the common case of only two keyframes
+ if (mNumKeyframes == 2) {
+ if (mInterpolator != null) {
+ fraction = mInterpolator.getInterpolation(fraction);
+ }
+ return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
+ mLastKeyframe.getValue());
+ }
+ if (fraction <= 0f) {
+ final Keyframe nextKeyframe = mKeyframes.get(1);
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ final float prevFraction = mFirstKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (nextKeyframe.getFraction() - prevFraction);
+ return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
+ nextKeyframe.getValue());
+ } else if (fraction >= 1f) {
+ final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
+ final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
+ if (interpolator != null) {
+ fraction = interpolator.getInterpolation(fraction);
+ }
+ final float prevFraction = prevKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (mLastKeyframe.getFraction() - prevFraction);
+ return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+ mLastKeyframe.getValue());
+ }
+ Keyframe prevKeyframe = mFirstKeyframe;
+ for (int i = 1; i < mNumKeyframes; ++i) {
+ Keyframe nextKeyframe = mKeyframes.get(i);
+ if (fraction < nextKeyframe.getFraction()) {
+ final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
+ final float prevFraction = prevKeyframe.getFraction();
+ float intervalFraction = (fraction - prevFraction) /
+ (nextKeyframe.getFraction() - prevFraction);
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
+ return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+ nextKeyframe.getValue());
+ }
+ prevKeyframe = nextKeyframe;
+ }
+ // shouldn't reach here
+ return mLastKeyframe.getValue();
+ }
+
+ @Override
+ public String toString() {
+ String returnVal = " ";
+ for (int i = 0; i < mNumKeyframes; ++i) {
+ returnVal += mKeyframes.get(i).getValue() + " ";
+ }
+ return returnVal;
+ }
+}
diff --git a/android-34/android/animation/Keyframes.java b/android-34/android/animation/Keyframes.java
new file mode 100644
index 0000000..6666294
--- /dev/null
+++ b/android-34/android/animation/Keyframes.java
@@ -0,0 +1,89 @@
+/*
+ * 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.animation;
+
+import java.util.List;
+
+/**
+ * This interface abstracts a collection of Keyframe objects and is called by
+ * ValueAnimator to calculate values between those keyframes for a given animation.
+ * @hide
+ */
+public interface Keyframes extends Cloneable {
+
+ /**
+ * Sets the TypeEvaluator to be used when calculating animated values. This object
+ * is required only for Keyframes that are not either IntKeyframes or FloatKeyframes,
+ * both of which assume their own evaluator to speed up calculations with those primitive
+ * types.
+ *
+ * @param evaluator The TypeEvaluator to be used to calculate animated values.
+ */
+ void setEvaluator(TypeEvaluator evaluator);
+
+ /**
+ * @return The value type contained by the contained Keyframes.
+ */
+ Class getType();
+
+ /**
+ * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+ * animation's interpolator) and the evaluator used to calculate in-between values. This
+ * function maps the input fraction to the appropriate keyframe interval and a fraction
+ * between them and returns the interpolated value. Note that the input fraction may fall
+ * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+ * spring interpolation that might send the fraction past 1.0). We handle this situation by
+ * just using the two keyframes at the appropriate end when the value is outside those bounds.
+ *
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ Object getValue(float fraction);
+
+ /**
+ * @return A list of all Keyframes contained by this. This may return null if this is
+ * not made up of Keyframes.
+ */
+ List<Keyframe> getKeyframes();
+
+ Keyframes clone();
+
+ /**
+ * A specialization of Keyframes that has integer primitive value calculation.
+ */
+ public interface IntKeyframes extends Keyframes {
+
+ /**
+ * Works like {@link #getValue(float)}, but returning a primitive.
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ int getIntValue(float fraction);
+ }
+
+ /**
+ * A specialization of Keyframes that has float primitive value calculation.
+ */
+ public interface FloatKeyframes extends Keyframes {
+
+ /**
+ * Works like {@link #getValue(float)}, but returning a primitive.
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ float getFloatValue(float fraction);
+ }
+}
diff --git a/android-34/android/animation/LayoutTransition.java b/android-34/android/animation/LayoutTransition.java
new file mode 100644
index 0000000..21f0b6b
--- /dev/null
+++ b/android-34/android/animation/LayoutTransition.java
@@ -0,0 +1,1545 @@
+/*
+ * 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.animation;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class enables automatic animations on layout changes in ViewGroup objects. To enable
+ * transitions for a layout container, create a LayoutTransition object and set it on any
+ * ViewGroup by calling {@link ViewGroup#setLayoutTransition(LayoutTransition)}. This will cause
+ * default animations to run whenever items are added to or removed from that container. To specify
+ * custom animations, use the {@link LayoutTransition#setAnimator(int, Animator)
+ * setAnimator()} method.
+ *
+ * <p>One of the core concepts of these transition animations is that there are two types of
+ * changes that cause the transition and four different animations that run because of
+ * those changes. The changes that trigger the transition are items being added to a container
+ * (referred to as an "appearing" transition) or removed from a container (also known as
+ * "disappearing"). Setting the visibility of views (between GONE and VISIBLE) will trigger
+ * the same add/remove logic. The animations that run due to those events are one that animates
+ * items being added, one that animates items being removed, and two that animate the other
+ * items in the container that change due to the add/remove occurrence. Users of
+ * the transition may want different animations for the changing items depending on whether
+ * they are changing due to an appearing or disappearing event, so there is one animation for
+ * each of these variations of the changing event. Most of the API of this class is concerned
+ * with setting up the basic properties of the animations used in these four situations,
+ * or with setting up custom animations for any or all of the four.</p>
+ *
+ * <p>By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING
+ * animation. The other animations begin after a delay that is set to the default duration
+ * of the animations. This behavior facilitates a sequence of animations in transitions as
+ * follows: when an item is being added to a layout, the other children of that container will
+ * move first (thus creating space for the new item), then the appearing animation will run to
+ * animate the item being added. Conversely, when an item is removed from a container, the
+ * animation to remove it will run first, then the animations of the other children in the
+ * layout will run (closing the gap created in the layout when the item was removed). If this
+ * default choreography behavior is not desired, the {@link #setDuration(int, long)} and
+ * {@link #setStartDelay(int, long)} of any or all of the animations can be changed as
+ * appropriate. Keep in mind, however, that if you start an APPEARING animation before a
+ * DISAPPEARING animation is completed, the DISAPPEARING animation stops, and any effects from
+ * the DISAPPEARING animation are reverted. If you instead start a DISAPPEARING animation
+ * before an APPEARING animation is completed, a similar set of effects occurs for the
+ * APPEARING animation.</p>
+ *
+ * <p>The animations specified for the transition, both the defaults and any custom animations
+ * set on the transition object, are templates only. That is, these animations exist to hold the
+ * basic animation properties, such as the duration, start delay, and properties being animated.
+ * But the actual target object, as well as the start and end values for those properties, are
+ * set automatically in the process of setting up the transition each time it runs. Each of the
+ * animations is cloned from the original copy and the clone is then populated with the dynamic
+ * values of the target being animated (such as one of the items in a layout container that is
+ * moving as a result of the layout event) as well as the values that are changing (such as the
+ * position and size of that object). The actual values that are pushed to each animation
+ * depends on what properties are specified for the animation. For example, the default
+ * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
+ * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
+ * Values for these properties are updated with the pre- and post-layout
+ * values when the transition begins. Custom animations will be similarly populated with
+ * the target and values being animated, assuming they use ObjectAnimator objects with
+ * property names that are known on the target object.</p>
+ *
+ * <p>This class, and the associated XML flag for containers, animateLayoutChanges="true",
+ * provides a simple utility meant for automating changes in straightforward situations.
+ * Using LayoutTransition at multiple levels of a nested view hierarchy may not work due to the
+ * interrelationship of the various levels of layout. Also, a container that is being scrolled
+ * at the same time as items are being added or removed is probably not a good candidate for
+ * this utility, because the before/after locations calculated by LayoutTransition
+ * may not match the actual locations when the animations finish due to the container
+ * being scrolled as the animations are running. You can work around that
+ * particular issue by disabling the 'changing' animations by setting the CHANGE_APPEARING
+ * and CHANGE_DISAPPEARING animations to null, and setting the startDelay of the
+ * other animations appropriately.</p>
+ */
+public class LayoutTransition {
+
+ /**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to a new item appearing in the container.
+ */
+ public static final int CHANGE_APPEARING = 0;
+
+ /**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to an item disappearing from the container.
+ */
+ public static final int CHANGE_DISAPPEARING = 1;
+
+ /**
+ * A flag indicating the animation that runs on those items that are appearing
+ * in the container.
+ */
+ public static final int APPEARING = 2;
+
+ /**
+ * A flag indicating the animation that runs on those items that are disappearing
+ * from the container.
+ */
+ public static final int DISAPPEARING = 3;
+
+ /**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to a layout change not caused by items being added to or removed
+ * from the container. This transition type is not enabled by default; it can be
+ * enabled via {@link #enableTransitionType(int)}.
+ */
+ public static final int CHANGING = 4;
+
+ /**
+ * Private bit fields used to set the collection of enabled transition types for
+ * mTransitionTypes.
+ */
+ private static final int FLAG_APPEARING = 0x01;
+ private static final int FLAG_DISAPPEARING = 0x02;
+ private static final int FLAG_CHANGE_APPEARING = 0x04;
+ private static final int FLAG_CHANGE_DISAPPEARING = 0x08;
+ private static final int FLAG_CHANGING = 0x10;
+
+ /**
+ * These variables hold the animations that are currently used to run the transition effects.
+ * These animations are set to defaults, but can be changed to custom animations by
+ * calls to setAnimator().
+ */
+ private Animator mDisappearingAnim = null;
+ private Animator mAppearingAnim = null;
+ private Animator mChangingAppearingAnim = null;
+ private Animator mChangingDisappearingAnim = null;
+ private Animator mChangingAnim = null;
+
+ /**
+ * These are the default animations, defined in the constructor, that will be used
+ * unless the user specifies custom animations.
+ */
+ private static ObjectAnimator defaultChange;
+ private static ObjectAnimator defaultChangeIn;
+ private static ObjectAnimator defaultChangeOut;
+ private static ObjectAnimator defaultFadeIn;
+ private static ObjectAnimator defaultFadeOut;
+
+ /**
+ * The default duration used by all animations.
+ */
+ private static long DEFAULT_DURATION = 300;
+
+ /**
+ * The durations of the different animations
+ */
+ private long mChangingAppearingDuration = DEFAULT_DURATION;
+ private long mChangingDisappearingDuration = DEFAULT_DURATION;
+ private long mChangingDuration = DEFAULT_DURATION;
+ private long mAppearingDuration = DEFAULT_DURATION;
+ private long mDisappearingDuration = DEFAULT_DURATION;
+
+ /**
+ * The start delays of the different animations. Note that the default behavior of
+ * the appearing item is the default duration, since it should wait for the items to move
+ * before fading it. Same for the changing animation when disappearing; it waits for the item
+ * to fade out before moving the other items.
+ */
+ private long mAppearingDelay = DEFAULT_DURATION;
+ private long mDisappearingDelay = 0;
+ private long mChangingAppearingDelay = 0;
+ private long mChangingDisappearingDelay = DEFAULT_DURATION;
+ private long mChangingDelay = 0;
+
+ /**
+ * The inter-animation delays used on the changing animations
+ */
+ private long mChangingAppearingStagger = 0;
+ private long mChangingDisappearingStagger = 0;
+ private long mChangingStagger = 0;
+
+ /**
+ * Static interpolators - these are stateless and can be shared across the instances
+ */
+ private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR =
+ new AccelerateDecelerateInterpolator();
+ private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator();
+ private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+ private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR;
+
+ /**
+ * The default interpolators used for the animations
+ */
+ private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator;
+ private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator;
+ private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator;
+ private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator;
+ private TimeInterpolator mChangingInterpolator = sChangingInterpolator;
+
+ /**
+ * These hashmaps are used to store the animations that are currently running as part of
+ * the transition. The reason for this is that a further layout event should cause
+ * existing animations to stop where they are prior to starting new animations. So
+ * we cache all of the current animations in this map for possible cancellation on
+ * another layout event. LinkedHashMaps are used to preserve the order in which animations
+ * are inserted, so that we process events (such as setting up start values) in the same order.
+ */
+ private final HashMap<View, Animator> pendingAnimations =
+ new HashMap<View, Animator>();
+ private final LinkedHashMap<View, Animator> currentChangingAnimations =
+ new LinkedHashMap<View, Animator>();
+ private final LinkedHashMap<View, Animator> currentAppearingAnimations =
+ new LinkedHashMap<View, Animator>();
+ private final LinkedHashMap<View, Animator> currentDisappearingAnimations =
+ new LinkedHashMap<View, Animator>();
+
+ /**
+ * This hashmap is used to track the listeners that have been added to the children of
+ * a container. When a layout change occurs, an animation is created for each View, so that
+ * the pre-layout values can be cached in that animation. Then a listener is added to the
+ * view to see whether the layout changes the bounds of that view. If so, the animation
+ * is set with the final values and then run. If not, the animation is not started. When
+ * the process of setting up and running all appropriate animations is done, we need to
+ * remove these listeners and clear out the map.
+ */
+ private final HashMap<View, View.OnLayoutChangeListener> layoutChangeListenerMap =
+ new HashMap<View, View.OnLayoutChangeListener>();
+
+ /**
+ * Used to track the current delay being assigned to successive animations as they are
+ * started. This value is incremented for each new animation, then zeroed before the next
+ * transition begins.
+ */
+ private long staggerDelay;
+
+ /**
+ * These are the types of transition animations that the LayoutTransition is reacting
+ * to. By default, appearing/disappearing and the change animations related to them are
+ * enabled (not CHANGING).
+ */
+ private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
+ FLAG_APPEARING | FLAG_DISAPPEARING;
+ /**
+ * The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
+ * start and end.
+ */
+ private ArrayList<TransitionListener> mListeners;
+
+ /**
+ * Controls whether changing animations automatically animate the parent hierarchy as well.
+ * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
+ * transition begins, causing visual glitches and clipping.
+ * Default value is true.
+ */
+ private boolean mAnimateParentHierarchy = true;
+
+
+ /**
+ * Constructs a LayoutTransition object. By default, the object will listen to layout
+ * events on any ViewGroup that it is set on and will run default animations for each
+ * type of layout event.
+ */
+ public LayoutTransition() {
+ if (defaultChangeIn == null) {
+ // "left" is just a placeholder; we'll put real properties/values in when needed
+ PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
+ PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
+ PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
+ PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
+ PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
+ PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
+ defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
+ pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
+ defaultChangeIn.setDuration(DEFAULT_DURATION);
+ defaultChangeIn.setStartDelay(mChangingAppearingDelay);
+ defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
+ defaultChangeOut = defaultChangeIn.clone();
+ defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
+ defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+ defaultChange = defaultChangeIn.clone();
+ defaultChange.setStartDelay(mChangingDelay);
+ defaultChange.setInterpolator(mChangingInterpolator);
+
+ defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
+ defaultFadeIn.setDuration(DEFAULT_DURATION);
+ defaultFadeIn.setStartDelay(mAppearingDelay);
+ defaultFadeIn.setInterpolator(mAppearingInterpolator);
+ defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
+ defaultFadeOut.setDuration(DEFAULT_DURATION);
+ defaultFadeOut.setStartDelay(mDisappearingDelay);
+ defaultFadeOut.setInterpolator(mDisappearingInterpolator);
+ }
+ mChangingAppearingAnim = defaultChangeIn;
+ mChangingDisappearingAnim = defaultChangeOut;
+ mChangingAnim = defaultChange;
+ mAppearingAnim = defaultFadeIn;
+ mDisappearingAnim = defaultFadeOut;
+ }
+
+ /**
+ * Sets the duration to be used by all animations of this transition object. If you want to
+ * set the duration of just one of the animations in particular, use the
+ * {@link #setDuration(int, long)} method.
+ *
+ * @param duration The length of time, in milliseconds, that the transition animations
+ * should last.
+ */
+ public void setDuration(long duration) {
+ mChangingAppearingDuration = duration;
+ mChangingDisappearingDuration = duration;
+ mChangingDuration = duration;
+ mAppearingDuration = duration;
+ mDisappearingDuration = duration;
+ }
+
+ /**
+ * Enables the specified transitionType for this LayoutTransition object.
+ * By default, a LayoutTransition listens for changes in children being
+ * added/remove/hidden/shown in the container, and runs the animations associated with
+ * those events. That is, all transition types besides {@link #CHANGING} are enabled by default.
+ * You can also enable {@link #CHANGING} animations by calling this method with the
+ * {@link #CHANGING} transitionType.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ */
+ public void enableTransitionType(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ mTransitionTypes |= FLAG_APPEARING;
+ break;
+ case DISAPPEARING:
+ mTransitionTypes |= FLAG_DISAPPEARING;
+ break;
+ case CHANGE_APPEARING:
+ mTransitionTypes |= FLAG_CHANGE_APPEARING;
+ break;
+ case CHANGE_DISAPPEARING:
+ mTransitionTypes |= FLAG_CHANGE_DISAPPEARING;
+ break;
+ case CHANGING:
+ mTransitionTypes |= FLAG_CHANGING;
+ break;
+ }
+ }
+
+ /**
+ * Disables the specified transitionType for this LayoutTransition object.
+ * By default, all transition types except {@link #CHANGING} are enabled.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ */
+ public void disableTransitionType(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ mTransitionTypes &= ~FLAG_APPEARING;
+ break;
+ case DISAPPEARING:
+ mTransitionTypes &= ~FLAG_DISAPPEARING;
+ break;
+ case CHANGE_APPEARING:
+ mTransitionTypes &= ~FLAG_CHANGE_APPEARING;
+ break;
+ case CHANGE_DISAPPEARING:
+ mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING;
+ break;
+ case CHANGING:
+ mTransitionTypes &= ~FLAG_CHANGING;
+ break;
+ }
+ }
+
+ /**
+ * Returns whether the specified transitionType is enabled for this LayoutTransition object.
+ * By default, all transition types except {@link #CHANGING} are enabled.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ * @return true if the specified transitionType is currently enabled, false otherwise.
+ */
+ public boolean isTransitionTypeEnabled(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING;
+ case DISAPPEARING:
+ return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING;
+ case CHANGE_APPEARING:
+ return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING;
+ case CHANGE_DISAPPEARING:
+ return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING;
+ case CHANGING:
+ return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING;
+ }
+ return false;
+ }
+
+ /**
+ * Sets the start delay on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose start delay
+ * is being set.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose start delay is being set.
+ * @param delay The length of time, in milliseconds, to delay before starting the animation.
+ * @see Animator#setStartDelay(long)
+ */
+ public void setStartDelay(int transitionType, long delay) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingDelay = delay;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingDelay = delay;
+ break;
+ case CHANGING:
+ mChangingDelay = delay;
+ break;
+ case APPEARING:
+ mAppearingDelay = delay;
+ break;
+ case DISAPPEARING:
+ mDisappearingDelay = delay;
+ break;
+ }
+ }
+
+ /**
+ * Gets the start delay on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose start delay
+ * is returned.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose start delay is returned.
+ * @return long The start delay of the specified animation.
+ * @see Animator#getStartDelay()
+ */
+ public long getStartDelay(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingDelay;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingDelay;
+ case CHANGING:
+ return mChangingDelay;
+ case APPEARING:
+ return mAppearingDelay;
+ case DISAPPEARING:
+ return mDisappearingDelay;
+ }
+ // shouldn't reach here
+ return 0;
+ }
+
+ /**
+ * Sets the duration on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose duration
+ * is being set.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose duration is being set.
+ * @param duration The length of time, in milliseconds, that the specified animation should run.
+ * @see Animator#setDuration(long)
+ */
+ public void setDuration(int transitionType, long duration) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingDuration = duration;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingDuration = duration;
+ break;
+ case CHANGING:
+ mChangingDuration = duration;
+ break;
+ case APPEARING:
+ mAppearingDuration = duration;
+ break;
+ case DISAPPEARING:
+ mDisappearingDuration = duration;
+ break;
+ }
+ }
+
+ /**
+ * Gets the duration on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose duration
+ * is returned.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose duration is returned.
+ * @return long The duration of the specified animation.
+ * @see Animator#getDuration()
+ */
+ public long getDuration(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingDuration;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingDuration;
+ case CHANGING:
+ return mChangingDuration;
+ case APPEARING:
+ return mAppearingDuration;
+ case DISAPPEARING:
+ return mDisappearingDuration;
+ }
+ // shouldn't reach here
+ return 0;
+ }
+
+ /**
+ * Sets the length of time to delay between starting each animation during one of the
+ * change animations.
+ *
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
+ * {@link #CHANGING}.
+ * @param duration The length of time, in milliseconds, to delay before launching the next
+ * animation in the sequence.
+ */
+ public void setStagger(int transitionType, long duration) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingStagger = duration;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingStagger = duration;
+ break;
+ case CHANGING:
+ mChangingStagger = duration;
+ break;
+ // noop other cases
+ }
+ }
+
+ /**
+ * Gets the length of time to delay between starting each animation during one of the
+ * change animations.
+ *
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
+ * {@link #CHANGING}.
+ * @return long The length of time, in milliseconds, to delay before launching the next
+ * animation in the sequence.
+ */
+ public long getStagger(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingStagger;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingStagger;
+ case CHANGING:
+ return mChangingStagger;
+ }
+ // shouldn't reach here
+ return 0;
+ }
+
+ /**
+ * Sets the interpolator on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose interpolator
+ * is being set.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose interpolator is being set.
+ * @param interpolator The interpolator that the specified animation should use.
+ * @see Animator#setInterpolator(TimeInterpolator)
+ */
+ public void setInterpolator(int transitionType, TimeInterpolator interpolator) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingInterpolator = interpolator;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingInterpolator = interpolator;
+ break;
+ case CHANGING:
+ mChangingInterpolator = interpolator;
+ break;
+ case APPEARING:
+ mAppearingInterpolator = interpolator;
+ break;
+ case DISAPPEARING:
+ mDisappearingInterpolator = interpolator;
+ break;
+ }
+ }
+
+ /**
+ * Gets the interpolator on one of the animation objects used by this transition. The
+ * <code>transitionType</code> parameter determines the animation whose interpolator
+ * is returned.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose interpolator is being returned.
+ * @return TimeInterpolator The interpolator that the specified animation uses.
+ * @see Animator#setInterpolator(TimeInterpolator)
+ */
+ public TimeInterpolator getInterpolator(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingInterpolator;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingInterpolator;
+ case CHANGING:
+ return mChangingInterpolator;
+ case APPEARING:
+ return mAppearingInterpolator;
+ case DISAPPEARING:
+ return mDisappearingInterpolator;
+ }
+ // shouldn't reach here
+ return null;
+ }
+
+ /**
+ * Sets the animation used during one of the transition types that may run. Any
+ * Animator object can be used, but to be most useful in the context of layout
+ * transitions, the animation should either be a ObjectAnimator or a AnimatorSet
+ * of animations including PropertyAnimators. Also, these ObjectAnimator objects
+ * should be able to get and set values on their target objects automatically. For
+ * example, a ObjectAnimator that animates the property "left" is able to set and get the
+ * <code>left</code> property from the View objects being animated by the layout
+ * transition. The transition works by setting target objects and properties
+ * dynamically, according to the pre- and post-layoout values of those objects, so
+ * having animations that can handle those properties appropriately will work best
+ * for custom animation. The dynamic setting of values is only the case for the
+ * CHANGE animations; the APPEARING and DISAPPEARING animations are simply run with
+ * the values they have.
+ *
+ * <p>It is also worth noting that any and all animations (and their underlying
+ * PropertyValuesHolder objects) will have their start and end values set according
+ * to the pre- and post-layout values. So, for example, a custom animation on "alpha"
+ * as the CHANGE_APPEARING animation will inherit the real value of alpha on the target
+ * object (presumably 1) as its starting and ending value when the animation begins.
+ * Animations which need to use values at the beginning and end that may not match the
+ * values queried when the transition begins may need to use a different mechanism
+ * than a standard ObjectAnimator object.</p>
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the
+ * animation whose animator is being set.
+ * @param animator The animation being assigned. A value of <code>null</code> means that no
+ * animation will be run for the specified transitionType.
+ */
+ public void setAnimator(int transitionType, Animator animator) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ mChangingAppearingAnim = animator;
+ break;
+ case CHANGE_DISAPPEARING:
+ mChangingDisappearingAnim = animator;
+ break;
+ case CHANGING:
+ mChangingAnim = animator;
+ break;
+ case APPEARING:
+ mAppearingAnim = animator;
+ break;
+ case DISAPPEARING:
+ mDisappearingAnim = animator;
+ break;
+ }
+ }
+
+ /**
+ * Gets the animation used during one of the transition types that may run.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose animator is being returned.
+ * @return Animator The animation being used for the given transition type.
+ * @see #setAnimator(int, Animator)
+ */
+ public Animator getAnimator(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ return mChangingAppearingAnim;
+ case CHANGE_DISAPPEARING:
+ return mChangingDisappearingAnim;
+ case CHANGING:
+ return mChangingAnim;
+ case APPEARING:
+ return mAppearingAnim;
+ case DISAPPEARING:
+ return mDisappearingAnim;
+ }
+ // shouldn't reach here
+ return null;
+ }
+
+ /**
+ * This function sets up animations on all of the views that change during layout.
+ * For every child in the parent, we create a change animation of the appropriate
+ * type (appearing, disappearing, or changing) and ask it to populate its start values from its
+ * target view. We add layout listeners to all child views and listen for changes. For
+ * those views that change, we populate the end values for those animations and start them.
+ * Animations are not run on unchanging views.
+ *
+ * @param parent The container which is undergoing a change.
+ * @param newView The view being added to or removed from the parent. May be null if the
+ * changeReason is CHANGING.
+ * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the
+ * transition is occurring because an item is being added to or removed from the parent, or
+ * if it is running in response to a layout operation (that is, if the value is CHANGING).
+ */
+ private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
+
+ Animator baseAnimator = null;
+ Animator parentAnimator = null;
+ final long duration;
+ switch (changeReason) {
+ case APPEARING:
+ baseAnimator = mChangingAppearingAnim;
+ duration = mChangingAppearingDuration;
+ parentAnimator = defaultChangeIn;
+ break;
+ case DISAPPEARING:
+ baseAnimator = mChangingDisappearingAnim;
+ duration = mChangingDisappearingDuration;
+ parentAnimator = defaultChangeOut;
+ break;
+ case CHANGING:
+ baseAnimator = mChangingAnim;
+ duration = mChangingDuration;
+ parentAnimator = defaultChange;
+ break;
+ default:
+ // Shouldn't reach here
+ duration = 0;
+ break;
+ }
+ // If the animation is null, there's nothing to do
+ if (baseAnimator == null) {
+ return;
+ }
+
+ // reset the inter-animation delay, in case we use it later
+ staggerDelay = 0;
+
+ final ViewTreeObserver observer = parent.getViewTreeObserver();
+ if (!observer.isAlive()) {
+ // If the observer's not in a good state, skip the transition
+ return;
+ }
+ int numChildren = parent.getChildCount();
+
+ for (int i = 0; i < numChildren; ++i) {
+ final View child = parent.getChildAt(i);
+
+ // only animate the views not being added or removed
+ if (child != newView) {
+ setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
+ }
+ }
+ if (mAnimateParentHierarchy) {
+ ViewGroup tempParent = parent;
+ while (tempParent != null) {
+ ViewParent parentParent = tempParent.getParent();
+ if (parentParent instanceof ViewGroup) {
+ setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator,
+ duration, tempParent);
+ tempParent = (ViewGroup) parentParent;
+ } else {
+ tempParent = null;
+ }
+
+ }
+ }
+
+ // This is the cleanup step. When we get this rendering event, we know that all of
+ // the appropriate animations have been set up and run. Now we can clear out the
+ // layout listeners.
+ CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent);
+ observer.addOnPreDrawListener(callback);
+ parent.addOnAttachStateChangeListener(callback);
+ }
+
+ /**
+ * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
+ * cause the default changing animation to be run on the parent hierarchy as well. This allows
+ * containers of transitioning views to also transition, which may be necessary in situations
+ * where the containers bounds change between the before/after states and may clip their
+ * children during the transition animations. For example, layouts with wrap_content will
+ * adjust their bounds according to the dimensions of their children.
+ *
+ * <p>The default changing transitions animate the bounds and scroll positions of the
+ * target views. These are the animations that will run on the parent hierarchy, not
+ * the custom animations that happen to be set on the transition. This allows custom
+ * behavior for the children of the transitioning container, but uses standard behavior
+ * of resizing/rescrolling on any changing parents.
+ *
+ * @param animateParentHierarchy A boolean value indicating whether the parents of
+ * transitioning views should also be animated during the transition. Default value is true.
+ */
+ public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
+ mAnimateParentHierarchy = animateParentHierarchy;
+ }
+
+ /**
+ * Utility function called by runChangingTransition for both the children and the parent
+ * hierarchy.
+ */
+ private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
+ Animator baseAnimator, final long duration, final View child) {
+
+ // If we already have a listener for this child, then we've already set up the
+ // changing animation we need. Multiple calls for a child may occur when several
+ // add/remove operations are run at once on a container; each one will trigger
+ // changes for the existing children in the container.
+ if (layoutChangeListenerMap.get(child) != null) {
+ return;
+ }
+
+ // Don't animate items up from size(0,0); this is likely because the objects
+ // were offscreen/invisible or otherwise measured to be infinitely small. We don't
+ // want to see them animate into their real size; just ignore animation requests
+ // on these views
+ if (child.getWidth() == 0 && child.getHeight() == 0) {
+ return;
+ }
+
+ // Make a copy of the appropriate animation
+ final Animator anim = baseAnimator.clone();
+
+ // Set the target object for the animation
+ anim.setTarget(child);
+
+ // A ObjectAnimator (or AnimatorSet of them) can extract start values from
+ // its target object
+ anim.setupStartValues();
+
+ // If there's an animation running on this view already, cancel it
+ Animator currentAnimation = pendingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ pendingAnimations.put(child, anim);
+
+ // For the animations which don't get started, we have to have a means of
+ // removing them from the cache, lest we leak them and their target objects.
+ // We run an animator for the default duration+100 (an arbitrary time, but one
+ // which should far surpass the delay between setting them up here and
+ // handling layout events which start them.
+ ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
+ setDuration(duration + 100);
+ pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ pendingAnimations.remove(child);
+ }
+ });
+ pendingAnimRemover.start();
+
+ // Add a listener to track layout changes on this view. If we don't get a callback,
+ // then there's nothing to animate.
+ final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+
+ // Tell the animation to extract end values from the changed object
+ anim.setupEndValues();
+ if (anim instanceof ValueAnimator) {
+ boolean valuesDiffer = false;
+ ValueAnimator valueAnim = (ValueAnimator)anim;
+ PropertyValuesHolder[] oldValues = valueAnim.getValues();
+ for (int i = 0; i < oldValues.length; ++i) {
+ PropertyValuesHolder pvh = oldValues[i];
+ if (pvh.mKeyframes instanceof KeyframeSet) {
+ KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes;
+ if (keyframeSet.mFirstKeyframe == null ||
+ keyframeSet.mLastKeyframe == null ||
+ !keyframeSet.mFirstKeyframe.getValue().equals(
+ keyframeSet.mLastKeyframe.getValue())) {
+ valuesDiffer = true;
+ }
+ } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) {
+ valuesDiffer = true;
+ }
+ }
+ if (!valuesDiffer) {
+ return;
+ }
+ }
+
+ long startDelay = 0;
+ switch (changeReason) {
+ case APPEARING:
+ startDelay = mChangingAppearingDelay + staggerDelay;
+ staggerDelay += mChangingAppearingStagger;
+ if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
+ anim.setInterpolator(mChangingAppearingInterpolator);
+ }
+ break;
+ case DISAPPEARING:
+ startDelay = mChangingDisappearingDelay + staggerDelay;
+ staggerDelay += mChangingDisappearingStagger;
+ if (mChangingDisappearingInterpolator !=
+ sChangingDisappearingInterpolator) {
+ anim.setInterpolator(mChangingDisappearingInterpolator);
+ }
+ break;
+ case CHANGING:
+ startDelay = mChangingDelay + staggerDelay;
+ staggerDelay += mChangingStagger;
+ if (mChangingInterpolator != sChangingInterpolator) {
+ anim.setInterpolator(mChangingInterpolator);
+ }
+ break;
+ }
+ anim.setStartDelay(startDelay);
+ anim.setDuration(duration);
+
+ Animator prevAnimation = currentChangingAnimations.get(child);
+ if (prevAnimation != null) {
+ prevAnimation.cancel();
+ }
+ Animator pendingAnimation = pendingAnimations.get(child);
+ if (pendingAnimation != null) {
+ pendingAnimations.remove(child);
+ }
+ // Cache the animation in case we need to cancel it later
+ currentChangingAnimations.put(child, anim);
+
+ parent.requestTransitionStart(LayoutTransition.this);
+
+ // this only removes listeners whose views changed - must clear the
+ // other listeners later
+ child.removeOnLayoutChangeListener(this);
+ layoutChangeListenerMap.remove(child);
+ }
+ };
+ // Remove the animation from the cache when it ends
+ anim.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.startTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : changeReason == DISAPPEARING ?
+ CHANGE_DISAPPEARING : CHANGING);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ child.removeOnLayoutChangeListener(listener);
+ layoutChangeListenerMap.remove(child);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ currentChangingAnimations.remove(child);
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child,
+ changeReason == APPEARING ?
+ CHANGE_APPEARING : changeReason == DISAPPEARING ?
+ CHANGE_DISAPPEARING : CHANGING);
+ }
+ }
+ }
+ });
+
+ child.addOnLayoutChangeListener(listener);
+ // cache the listener for later removal
+ layoutChangeListenerMap.put(child, listener);
+ }
+
+ /**
+ * Starts the animations set up for a CHANGING transition. We separate the setup of these
+ * animations from actually starting them, to avoid side-effects that starting the animations
+ * may have on the properties of the affected objects. After setup, we tell the affected parent
+ * that this transition should be started. The parent informs its ViewAncestor, which then
+ * starts the transition after the current layout/measurement phase, just prior to drawing
+ * the view hierarchy.
+ *
+ * @hide
+ */
+ public void startChangingAnimations() {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ anim.start();
+ }
+ }
+
+ /**
+ * Ends the animations that are set up for a CHANGING transition. This is a variant of
+ * startChangingAnimations() which is called when the window the transition is playing in
+ * is not visible. We need to make sure the animations put their targets in their end states
+ * and that the transition finishes to remove any mid-process state (such as isRunning()).
+ *
+ * @hide
+ */
+ public void endChangingAnimations() {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.start();
+ anim.end();
+ }
+ // listeners should clean up the currentChangingAnimations list, but just in case...
+ currentChangingAnimations.clear();
+ }
+
+ /**
+ * Returns true if animations are running which animate layout-related properties. This
+ * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
+ * are running, since these animations operate on layout-related properties.
+ *
+ * @return true if CHANGE_APPEARING or CHANGE_DISAPPEARING animations are currently
+ * running.
+ */
+ public boolean isChangingLayout() {
+ return (currentChangingAnimations.size() > 0);
+ }
+
+ /**
+ * Returns true if any of the animations in this transition are currently running.
+ *
+ * @return true if any animations in the transition are running.
+ */
+ public boolean isRunning() {
+ return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
+ currentDisappearingAnimations.size() > 0);
+ }
+
+ /**
+ * Cancels the currently running transition. Note that we cancel() the changing animations
+ * but end() the visibility animations. This is because this method is currently called
+ * in the context of starting a new transition, so we want to move things from their mid-
+ * transition positions, but we want them to have their end-transition visibility.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public void cancel() {
+ if (currentChangingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.cancel();
+ }
+ currentChangingAnimations.clear();
+ }
+ if (currentAppearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentAppearingAnimations.clear();
+ }
+ if (currentDisappearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentDisappearingAnimations.clear();
+ }
+ }
+
+ /**
+ * Cancels the specified type of transition. Note that we cancel() the changing animations
+ * but end() the visibility animations. This is because this method is currently called
+ * in the context of starting a new transition, so we want to move things from their mid-
+ * transition positions, but we want them to have their end-transition visibility.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public void cancel(int transitionType) {
+ switch (transitionType) {
+ case CHANGE_APPEARING:
+ case CHANGE_DISAPPEARING:
+ case CHANGING:
+ if (currentChangingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.cancel();
+ }
+ currentChangingAnimations.clear();
+ }
+ break;
+ case APPEARING:
+ if (currentAppearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentAppearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentAppearingAnimations.clear();
+ }
+ break;
+ case DISAPPEARING:
+ if (currentDisappearingAnimations.size() > 0) {
+ LinkedHashMap<View, Animator> currentAnimCopy =
+ (LinkedHashMap<View, Animator>) currentDisappearingAnimations.clone();
+ for (Animator anim : currentAnimCopy.values()) {
+ anim.end();
+ }
+ currentDisappearingAnimations.clear();
+ }
+ break;
+ }
+ }
+
+ /**
+ * This method runs the animation that makes an added item appear.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ */
+ private void runAppearingTransition(final ViewGroup parent, final View child) {
+ Animator currentAnimation = currentDisappearingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ }
+ if (mAppearingAnim == null) {
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+ }
+ }
+ return;
+ }
+ Animator anim = mAppearingAnim.clone();
+ anim.setTarget(child);
+ anim.setStartDelay(mAppearingDelay);
+ anim.setDuration(mAppearingDuration);
+ if (mAppearingInterpolator != sAppearingInterpolator) {
+ anim.setInterpolator(mAppearingInterpolator);
+ }
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ currentAppearingAnimations.remove(child);
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+ }
+ }
+ }
+ });
+ currentAppearingAnimations.put(child, anim);
+ anim.start();
+ }
+
+ /**
+ * This method runs the animation that makes a removed item disappear.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ */
+ private void runDisappearingTransition(final ViewGroup parent, final View child) {
+ Animator currentAnimation = currentAppearingAnimations.get(child);
+ if (currentAnimation != null) {
+ currentAnimation.cancel();
+ }
+ if (mDisappearingAnim == null) {
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+ }
+ }
+ return;
+ }
+ Animator anim = mDisappearingAnim.clone();
+ anim.setStartDelay(mDisappearingDelay);
+ anim.setDuration(mDisappearingDuration);
+ if (mDisappearingInterpolator != sDisappearingInterpolator) {
+ anim.setInterpolator(mDisappearingInterpolator);
+ }
+ anim.setTarget(child);
+ final float preAnimAlpha = child.getAlpha();
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ currentDisappearingAnimations.remove(child);
+ child.setAlpha(preAnimAlpha);
+ if (hasListeners()) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+ }
+ }
+ }
+ });
+ if (anim instanceof ObjectAnimator) {
+ ((ObjectAnimator) anim).setCurrentPlayTime(0);
+ }
+ currentDisappearingAnimations.put(child, anim);
+ anim.start();
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be added to the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ * @param changesLayout Whether the removal will cause changes in the layout of other views
+ * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
+ * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
+ */
+ private void addChild(ViewGroup parent, View child, boolean changesLayout) {
+ if (parent.getWindowVisibility() != View.VISIBLE) {
+ return;
+ }
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ // Want disappearing animations to finish up before proceeding
+ cancel(DISAPPEARING);
+ }
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
+ // Also, cancel changing animations so that we start fresh ones from current locations
+ cancel(CHANGE_APPEARING);
+ cancel(CHANGING);
+ }
+ if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ ArrayList<TransitionListener> listeners =
+ (ArrayList<TransitionListener>) mListeners.clone();
+ for (TransitionListener listener : listeners) {
+ listener.startTransition(this, parent, child, APPEARING);
+ }
+ }
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
+ runChangeTransition(parent, child, APPEARING);
+ }
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ runAppearingTransition(parent, child);
+ }
+ }
+
+ private boolean hasListeners() {
+ return mListeners != null && mListeners.size() > 0;
+ }
+
+ /**
+ * This method is called by ViewGroup when there is a call to layout() on the container
+ * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other
+ * transition currently running on the container, then this call runs a CHANGING transition.
+ * The transition does not start immediately; it just sets up the mechanism to run if any
+ * of the children of the container change their layout parameters (similar to
+ * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions).
+ *
+ * @param parent The ViewGroup whose layout() method has been called.
+ *
+ * @hide
+ */
+ public void layoutChange(ViewGroup parent) {
+ if (parent.getWindowVisibility() != View.VISIBLE) {
+ return;
+ }
+ if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) {
+ // This method is called for all calls to layout() in the container, including
+ // those caused by add/remove/hide/show events, which will already have set up
+ // transition animations. Avoid setting up CHANGING animations in this case; only
+ // do so when there is not a transition already running on the container.
+ runChangeTransition(parent, null, CHANGING);
+ }
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be added to the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ */
+ public void addChild(ViewGroup parent, View child) {
+ addChild(parent, child, true);
+ }
+
+ /**
+ * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
+ */
+ @Deprecated
+ public void showChild(ViewGroup parent, View child) {
+ addChild(parent, child, true);
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be made visible in the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup in which the View is being made visible.
+ * @param child The View being made visible.
+ * @param oldVisibility The previous visibility value of the child View, either
+ * {@link View#GONE} or {@link View#INVISIBLE}.
+ */
+ public void showChild(ViewGroup parent, View child, int oldVisibility) {
+ addChild(parent, child, oldVisibility == View.GONE);
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be removed from the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ * @param changesLayout Whether the removal will cause changes in the layout of other views
+ * in the container. Views becoming INVISIBLE will not cause changes and thus will not
+ * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
+ */
+ private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
+ if (parent.getWindowVisibility() != View.VISIBLE) {
+ return;
+ }
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ // Want appearing animations to finish up before proceeding
+ cancel(APPEARING);
+ }
+ if (changesLayout &&
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
+ // Also, cancel changing animations so that we start fresh ones from current locations
+ cancel(CHANGE_DISAPPEARING);
+ cancel(CHANGING);
+ }
+ if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners
+ .clone();
+ for (TransitionListener listener : listeners) {
+ listener.startTransition(this, parent, child, DISAPPEARING);
+ }
+ }
+ if (changesLayout &&
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
+ runChangeTransition(parent, child, DISAPPEARING);
+ }
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ runDisappearingTransition(parent, child);
+ }
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be removed from the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ */
+ public void removeChild(ViewGroup parent, View child) {
+ removeChild(parent, child, true);
+ }
+
+ /**
+ * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
+ */
+ @Deprecated
+ public void hideChild(ViewGroup parent, View child) {
+ removeChild(parent, child, true);
+ }
+
+ /**
+ * This method is called by ViewGroup when a child view is about to be hidden in
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The parent ViewGroup of the View being hidden.
+ * @param child The View being hidden.
+ * @param newVisibility The new visibility value of the child View, either
+ * {@link View#GONE} or {@link View#INVISIBLE}.
+ */
+ public void hideChild(ViewGroup parent, View child, int newVisibility) {
+ removeChild(parent, child, newVisibility == View.GONE);
+ }
+
+ /**
+ * Add a listener that will be called when the bounds of the view change due to
+ * layout processing.
+ *
+ * @param listener The listener that will be called when layout bounds change.
+ */
+ public void addTransitionListener(TransitionListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<TransitionListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener for layout changes.
+ *
+ * @param listener The listener for layout bounds change.
+ */
+ public void removeTransitionListener(TransitionListener listener) {
+ if (mListeners == null) {
+ return;
+ }
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Gets the current list of listeners for layout changes.
+ * @return
+ */
+ public List<TransitionListener> getTransitionListeners() {
+ return mListeners;
+ }
+
+ /**
+ * This interface is used for listening to starting and ending events for transitions.
+ */
+ public interface TransitionListener {
+
+ /**
+ * This event is sent to listeners when any type of transition animation begins.
+ *
+ * @param transition The LayoutTransition sending out the event.
+ * @param container The ViewGroup on which the transition is playing.
+ * @param view The View object being affected by the transition animation.
+ * @param transitionType The type of transition that is beginning,
+ * {@link android.animation.LayoutTransition#APPEARING},
+ * {@link android.animation.LayoutTransition#DISAPPEARING},
+ * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
+ * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
+ */
+ public void startTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType);
+
+ /**
+ * This event is sent to listeners when any type of transition animation ends.
+ *
+ * @param transition The LayoutTransition sending out the event.
+ * @param container The ViewGroup on which the transition is playing.
+ * @param view The View object being affected by the transition animation.
+ * @param transitionType The type of transition that is ending,
+ * {@link android.animation.LayoutTransition#APPEARING},
+ * {@link android.animation.LayoutTransition#DISAPPEARING},
+ * {@link android.animation.LayoutTransition#CHANGE_APPEARING}, or
+ * {@link android.animation.LayoutTransition#CHANGE_DISAPPEARING}.
+ */
+ public void endTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType);
+ }
+
+ /**
+ * Utility class to clean up listeners after animations are setup. Cleanup happens
+ * when either the OnPreDrawListener method is called or when the parent is detached,
+ * whichever comes first.
+ */
+ private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener,
+ View.OnAttachStateChangeListener {
+
+ final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap;
+ final ViewGroup parent;
+
+ CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) {
+ this.layoutChangeListenerMap = listenerMap;
+ this.parent = parent;
+ }
+
+ private void cleanup() {
+ parent.getViewTreeObserver().removeOnPreDrawListener(this);
+ parent.removeOnAttachStateChangeListener(this);
+ int count = layoutChangeListenerMap.size();
+ if (count > 0) {
+ Collection<View> views = layoutChangeListenerMap.keySet();
+ for (View view : views) {
+ View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
+ view.removeOnLayoutChangeListener(listener);
+ }
+ layoutChangeListenerMap.clear();
+ }
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ cleanup();
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ cleanup();
+ return true;
+ }
+ };
+
+}
diff --git a/android-34/android/animation/ObjectAnimator.java b/android-34/android/animation/ObjectAnimator.java
new file mode 100644
index 0000000..1e1f155
--- /dev/null
+++ b/android-34/android/animation/ObjectAnimator.java
@@ -0,0 +1,1017 @@
+/*
+ * 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.animation;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.Log;
+import android.util.Property;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
+ * The constructors of this class take parameters to define the target object that will be animated
+ * as well as the name of the property that will be animated. Appropriate set/get functions
+ * are then determined internally and the animation will call these functions as necessary to
+ * animate the property.
+ *
+ * <p>Animators can be created from either code or resource files, as shown here:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator.xml ObjectAnimatorResources}
+ *
+ * <p>Starting from API 23, it is possible to use {@link PropertyValuesHolder} and
+ * {@link Keyframe} in resource files to create more complex animations. Using PropertyValuesHolders
+ * allows animators to animate several properties in parallel, as shown in this sample:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh.xml
+ * PropertyValuesHolderResources}
+ *
+ * <p>Using Keyframes allows animations to follow more complex paths from the start
+ * to the end values. Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration. Also, a keyframe with no value will derive its value
+ * from the target object when the animator starts, just like animators with only one
+ * value specified. In addition, an optional interpolator can be specified. The interpolator will
+ * be applied on the interval between the keyframe that the interpolator is set on and the previous
+ * keyframe. When no interpolator is supplied, the default {@link AccelerateDecelerateInterpolator}
+ * will be used. </p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf_interpolated.xml KeyframeResources}
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code ObjectAnimator}, read the
+ * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#object-animator">Property
+ * Animation</a> developer guide.</p>
+ * </div>
+ *
+ * @see #setPropertyName(String)
+ *
+ */
+public final class ObjectAnimator extends ValueAnimator {
+ private static final String LOG_TAG = "ObjectAnimator";
+
+ private static final boolean DBG = false;
+
+ /**
+ * A weak reference to the target object on which the property exists, set
+ * in the constructor. We'll cancel the animation if this goes away.
+ */
+ private WeakReference<Object> mTarget;
+
+ private String mPropertyName;
+
+ private Property mProperty;
+
+ private boolean mAutoCancel = false;
+
+ /**
+ * Sets the name of the property that will be animated. This name is used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>For best performance of the mechanism that calls the setter function determined by the
+ * name of the property being animated, use <code>float</code> or <code>int</code> typed values,
+ * and make the setter function for those properties have a <code>void</code> return value. This
+ * will cause the code to take an optimized path for these constrained circumstances. Other
+ * property types and return types will work, but will have more overhead in processing
+ * the requests due to normal reflection mechanisms.</p>
+ *
+ * <p>Note that the setter function derived from this property name
+ * must take the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+ * the setter function will fail.</p>
+ *
+ * <p>If this ObjectAnimator has been set up to animate several properties together,
+ * using more than one PropertyValuesHolder objects, then setting the propertyName simply
+ * sets the propertyName in the first of those PropertyValuesHolder objects.</p>
+ *
+ * @param propertyName The name of the property being animated. Should not be null.
+ */
+ public void setPropertyName(@NonNull String propertyName) {
+ // mValues could be null if this is being constructed piecemeal. Just record the
+ // propertyName to be used later when setValues() is called if so.
+ if (mValues != null) {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ String oldName = valuesHolder.getPropertyName();
+ valuesHolder.setPropertyName(propertyName);
+ mValuesMap.remove(oldName);
+ mValuesMap.put(propertyName, valuesHolder);
+ }
+ mPropertyName = propertyName;
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets the property that will be animated. Property objects will take precedence over
+ * properties specified by the {@link #setPropertyName(String)} method. Animations should
+ * be set up to use one or the other, not both.
+ *
+ * @param property The property being animated. Should not be null.
+ */
+ public void setProperty(@NonNull Property property) {
+ // mValues could be null if this is being constructed piecemeal. Just record the
+ // propertyName to be used later when setValues() is called if so.
+ if (mValues != null) {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ String oldName = valuesHolder.getPropertyName();
+ valuesHolder.setProperty(property);
+ mValuesMap.remove(oldName);
+ mValuesMap.put(mPropertyName, valuesHolder);
+ }
+ if (mProperty != null) {
+ mPropertyName = property.getName();
+ }
+ mProperty = property;
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Gets the name of the property that will be animated. This name will be used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>If this animator was created with a {@link Property} object instead of the
+ * string name of a property, then this method will return the {@link
+ * Property#getName() name} of that Property object instead. If this animator was
+ * created with one or more {@link PropertyValuesHolder} objects, then this method
+ * will return the {@link PropertyValuesHolder#getPropertyName() name} of that
+ * object (if there was just one) or a comma-separated list of all of the
+ * names (if there are more than one).</p>
+ */
+ @Nullable
+ public String getPropertyName() {
+ String propertyName = null;
+ if (mPropertyName != null) {
+ propertyName = mPropertyName;
+ } else if (mProperty != null) {
+ propertyName = mProperty.getName();
+ } else if (mValues != null && mValues.length > 0) {
+ for (int i = 0; i < mValues.length; ++i) {
+ if (i == 0) {
+ propertyName = "";
+ } else {
+ propertyName += ",";
+ }
+ propertyName += mValues[i].getPropertyName();
+ }
+ }
+ return propertyName;
+ }
+
+ @Override
+ String getNameForTrace() {
+ return "animator:" + getPropertyName();
+ }
+
+ /**
+ * Creates a new ObjectAnimator object. This default constructor is primarily for
+ * use internally; the other constructors which take parameters are more generally
+ * useful.
+ */
+ public ObjectAnimator() {
+ }
+
+ /**
+ * Private utility constructor that initializes the target object and name of the
+ * property being animated.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ */
+ private ObjectAnimator(Object target, String propertyName) {
+ setTarget(target);
+ setPropertyName(propertyName);
+ }
+
+ /**
+ * Private utility constructor that initializes the target object and property being animated.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ */
+ private <T> ObjectAnimator(T target, Property<T, ?> property) {
+ setTarget(target);
+ setProperty(property);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integers that are set to separate properties designated by
+ * <code>xPropertyName</code> and <code>yPropertyName</code>.
+ *
+ * @param target The object whose properties are to be animated. This object should
+ * have public methods on it called <code>setNameX()</code> and
+ * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+ * are the value of <code>xPropertyName</code> and <code>yPropertyName</code>
+ * parameters, respectively.
+ * @param xPropertyName The name of the property for the x coordinate being animated.
+ * @param yPropertyName The name of the property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
+ Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
+ keyframes.createXIntKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
+ keyframes.createYIntKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integers that are set to separate properties, <code>xProperty</code> and
+ * <code>yProperty</code>.
+ *
+ * @param target The object whose properties are to be animated.
+ * @param xProperty The property for the x coordinate being animated.
+ * @param yProperty The property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
+ Property<T, Integer> yProperty, Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty,
+ keyframes.createXIntKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty,
+ keyframes.createYIntKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over int values for a multiple
+ * parameters setter. Only public methods that take only int parameters are supported.
+ * Each <code>int[]</code> contains a complete set of parameters to the setter method.
+ * At least two <code>int[]</code> values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofMultiInt(Object target, String propertyName, int[][] values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates the target using a multi-int setter
+ * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions,
+ * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integer x and y coordinates used in the first and second parameter of the
+ * setter, respectively.
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofMultiInt(Object target, String propertyName, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over values for a multiple int
+ * parameters setter. Only public methods that take only int parameters are supported.
+ * <p>At least two values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).</p>
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param converter Converts T objects into int parameters for the multi-value setter.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @SafeVarargs
+ public static <T> ObjectAnimator ofMultiInt(Object target, String propertyName,
+ TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, converter,
+ evaluator, values);
+ return ObjectAnimator.ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
+ ObjectAnimator animator = ofInt(target, propertyName, values);
+ animator.setEvaluator(ArgbEvaluator.getInstance());
+ return animator;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property,
+ int... values) {
+ ObjectAnimator animator = ofInt(target, property, values);
+ animator.setEvaluator(ArgbEvaluator.getInstance());
+ return animator;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are floats that are set to separate properties designated by
+ * <code>xPropertyName</code> and <code>yPropertyName</code>.
+ *
+ * @param target The object whose properties are to be animated. This object should
+ * have public methods on it called <code>setNameX()</code> and
+ * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+ * are the value of the <code>xPropertyName</code> and <code>yPropertyName</code>
+ * parameters, respectively.
+ * @param xPropertyName The name of the property for the x coordinate being animated.
+ * @param yPropertyName The name of the property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
+ Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
+ keyframes.createXFloatKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
+ keyframes.createYFloatKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
+ float... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are floats that are set to separate properties, <code>xProperty</code> and
+ * <code>yProperty</code>.
+ *
+ * @param target The object whose properties are to be animated.
+ * @param xProperty The property for the x coordinate being animated.
+ * @param yProperty The property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
+ Property<T, Float> yProperty, Path path) {
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty,
+ keyframes.createXFloatKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty,
+ keyframes.createYFloatKeyframes());
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over float values for a multiple
+ * parameters setter. Only public methods that take only float parameters are supported.
+ * Each <code>float[]</code> contains a complete set of parameters to the setter method.
+ * At least two <code>float[]</code> values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofMultiFloat(Object target, String propertyName,
+ float[][] values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates the target using a multi-float setter
+ * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions,
+ * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are float x and y coordinates used in the first and second parameter of the
+ * setter, respectively.
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofMultiFloat(Object target, String propertyName, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over values for a multiple float
+ * parameters setter. Only public methods that take only float parameters are supported.
+ * <p>At least two values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).</p>
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param converter Converts T objects into float parameters for the multi-value setter.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @SafeVarargs
+ public static <T> ObjectAnimator ofMultiFloat(Object target, String propertyName,
+ TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, converter,
+ evaluator, values);
+ return ObjectAnimator.ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * <p><strong>Note:</strong> The values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofObject(Object target, String propertyName,
+ TypeEvaluator evaluator, Object... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>.
+ * A <code>Path</code></> animation moves in two dimensions, animating coordinates
+ * <code>(x, y)</code> together to follow the line. This variant animates the coordinates
+ * in a <code>PointF</code> to follow the <code>Path</code>. If the <code>Property</code>
+ * associated with <code>propertyName</code> uses a type other than <code>PointF</code>,
+ * <code>converter</code> can be used to change from <code>PointF</code> to the type
+ * associated with the <code>Property</code>.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ @NonNull
+ public static ObjectAnimator ofObject(Object target, String propertyName,
+ @Nullable TypeConverter<PointF, ?> converter, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(propertyName, converter, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ *
+ * <p><strong>Note:</strong> The values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @NonNull
+ @SafeVarargs
+ public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property,
+ TypeEvaluator<V> evaluator, V... values) {
+ ObjectAnimator anim = new ObjectAnimator(target, property);
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to, in which case the start value
+ * will be derived from the property being animated and the target object when {@link #start()}
+ * is called for the first time. Two values imply starting and ending values. More than two
+ * values imply a starting value, values to animate through along the way, and an ending value
+ * (these values will be distributed evenly across the duration of the animation).
+ * This variant supplies a <code>TypeConverter</code> to convert from the animated values to the
+ * type of the property. If only one value is supplied, the <code>TypeConverter</code> must be a
+ * {@link android.animation.BidirectionalTypeConverter} to retrieve the current value.
+ *
+ * <p><strong>Note:</strong> The values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param converter Converts the animated object to the Property type.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @NonNull
+ @SafeVarargs
+ public static <T, V, P> ObjectAnimator ofObject(T target, Property<T, P> property,
+ TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, evaluator,
+ values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>.
+ * A <code>Path</code></> animation moves in two dimensions, animating coordinates
+ * <code>(x, y)</code> together to follow the line. This variant animates the coordinates
+ * in a <code>PointF</code> to follow the <code>Path</code>. If <code>property</code>
+ * uses a type other than <code>PointF</code>, <code>converter</code> can be used to change
+ * from <code>PointF</code> to the type associated with the <code>Property</code>.
+ *
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ @NonNull
+ public static <T, V> ObjectAnimator ofObject(T target, @NonNull Property<T, V> property,
+ @Nullable TypeConverter<PointF, V> converter, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between the sets of values specified
+ * in <code>PropertyValueHolder</code> objects. This variant should be used when animating
+ * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows
+ * you to associate a set of animation values with a property name.
+ *
+ * @param target The object whose property is to be animated. Depending on how the
+ * PropertyValuesObjects were constructed, the target object should either have the {@link
+ * android.util.Property} objects used to construct the PropertyValuesHolder objects or (if the
+ * PropertyValuesHOlder objects were created with property names) the target object should have
+ * public methods on it called <code>setName()</code>, where <code>name</code> is the name of
+ * the property passed in as the <code>propertyName</code> parameter for each of the
+ * PropertyValuesHolder objects.
+ * @param values A set of PropertyValuesHolder objects whose values will be animated between
+ * over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ @NonNull
+ public static ObjectAnimator ofPropertyValuesHolder(Object target,
+ PropertyValuesHolder... values) {
+ ObjectAnimator anim = new ObjectAnimator();
+ anim.setTarget(target);
+ anim.setValues(values);
+ return anim;
+ }
+
+ @Override
+ public void setIntValues(int... values) {
+ if (mValues == null || mValues.length == 0) {
+ // No values yet - this animator is being constructed piecemeal. Init the values with
+ // whatever the current propertyName is
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofInt(mProperty, values));
+ } else {
+ setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
+ }
+ } else {
+ super.setIntValues(values);
+ }
+ }
+
+ @Override
+ public void setFloatValues(float... values) {
+ if (mValues == null || mValues.length == 0) {
+ // No values yet - this animator is being constructed piecemeal. Init the values with
+ // whatever the current propertyName is
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofFloat(mProperty, values));
+ } else {
+ setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
+ }
+ } else {
+ super.setFloatValues(values);
+ }
+ }
+
+ @Override
+ public void setObjectValues(Object... values) {
+ if (mValues == null || mValues.length == 0) {
+ // No values yet - this animator is being constructed piecemeal. Init the values with
+ // whatever the current propertyName is
+ if (mProperty != null) {
+ setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator) null, values));
+ } else {
+ setValues(PropertyValuesHolder.ofObject(mPropertyName,
+ (TypeEvaluator) null, values));
+ }
+ } else {
+ super.setObjectValues(values);
+ }
+ }
+
+ /**
+ * autoCancel controls whether an ObjectAnimator will be canceled automatically
+ * when any other ObjectAnimator with the same target and properties is started.
+ * Setting this flag may make it easier to run different animators on the same target
+ * object without having to keep track of whether there are conflicting animators that
+ * need to be manually canceled. Canceling animators must have the same exact set of
+ * target properties, in the same order.
+ *
+ * @param cancel Whether future ObjectAnimators with the same target and properties
+ * as this ObjectAnimator will cause this ObjectAnimator to be canceled.
+ */
+ public void setAutoCancel(boolean cancel) {
+ mAutoCancel = cancel;
+ }
+
+ private boolean hasSameTargetAndProperties(@Nullable Animator anim) {
+ if (anim instanceof ObjectAnimator) {
+ PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
+ if (((ObjectAnimator) anim).getTarget() == getTarget() &&
+ mValues.length == theirValues.length) {
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvhMine = mValues[i];
+ PropertyValuesHolder pvhTheirs = theirValues[i];
+ if (pvhMine.getPropertyName() == null ||
+ !pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void start() {
+ AnimationHandler.getInstance().autoCancelBasedOn(this);
+ if (DBG) {
+ Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvh = mValues[i];
+ Log.d(LOG_TAG, " Values[" + i + "]: " +
+ pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
+ pvh.mKeyframes.getValue(1));
+ }
+ }
+ super.start();
+ }
+
+ boolean shouldAutoCancel(AnimationHandler.AnimationFrameCallback anim) {
+ if (anim == null) {
+ return false;
+ }
+
+ if (anim instanceof ObjectAnimator) {
+ ObjectAnimator objAnim = (ObjectAnimator) anim;
+ if (objAnim.mAutoCancel && hasSameTargetAndProperties(objAnim)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This function is called immediately before processing the first animation
+ * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+ * function is called after that delay ends.
+ * It takes care of the final initialization steps for the
+ * animation. This includes setting mEvaluator, if the user has not yet
+ * set it up, and the setter/getter methods, if the user did not supply
+ * them.
+ *
+ * <p>Overriders of this method should call the superclass method to cause
+ * internal mechanisms to be set up correctly.</p>
+ */
+ @CallSuper
+ @Override
+ void initAnimation() {
+ if (!mInitialized) {
+ // mValueType may change due to setter/getter setup; do this before calling super.init(),
+ // which uses mValueType to set up the default type evaluator.
+ final Object target = getTarget();
+ if (target != null) {
+ final int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setupSetterAndGetter(target);
+ }
+ }
+ super.initAnimation();
+ }
+ }
+
+ /**
+ * Sets the length of the animation. The default duration is 300 milliseconds.
+ *
+ * @param duration The length of the animation, in milliseconds.
+ * @return ObjectAnimator The object called with setDuration(). This return
+ * value makes it easier to compose statements together that construct and then set the
+ * duration, as in
+ * <code>ObjectAnimator.ofInt(target, propertyName, 0, 10).setDuration(500).start()</code>.
+ */
+ @Override
+ @NonNull
+ public ObjectAnimator setDuration(long duration) {
+ super.setDuration(duration);
+ return this;
+ }
+
+
+ /**
+ * The target object whose property will be animated by this animation
+ *
+ * @return The object being animated
+ */
+ @Nullable
+ public Object getTarget() {
+ return mTarget == null ? null : mTarget.get();
+ }
+
+ @Override
+ public void setTarget(@Nullable Object target) {
+ final Object oldTarget = getTarget();
+ if (oldTarget != target) {
+ if (isStarted()) {
+ cancel();
+ }
+ mTarget = target == null ? null : new WeakReference<Object>(target);
+ // New target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+ }
+
+ @Override
+ public void setupStartValues() {
+ initAnimation();
+
+ final Object target = getTarget();
+ if (target != null) {
+ final int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setupStartValue(target);
+ }
+ }
+ }
+
+ @Override
+ public void setupEndValues() {
+ initAnimation();
+
+ final Object target = getTarget();
+ if (target != null) {
+ final int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setupEndValue(target);
+ }
+ }
+ }
+
+ /**
+ * This method is called with the elapsed fraction of the animation during every
+ * animation frame. This function turns the elapsed fraction into an interpolated fraction
+ * and then into an animated value (from the evaluator. The function is called mostly during
+ * animation updates, but it is also called when the <code>end()</code>
+ * function is called, to set the final value on the property.
+ *
+ * <p>Overrides of this method must call the superclass to perform the calculation
+ * of the animated value.</p>
+ *
+ * @param fraction The elapsed fraction of the animation.
+ */
+ @CallSuper
+ @Override
+ void animateValue(float fraction) {
+ final Object target = getTarget();
+ if (mTarget != null && target == null) {
+ // We lost the target reference, cancel and clean up. Note: we allow null target if the
+ /// target has never been set.
+ cancel();
+ return;
+ }
+
+ super.animateValue(fraction);
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].setAnimatedValue(target);
+ }
+ }
+
+ @Override
+ boolean isInitialized() {
+ return mInitialized;
+ }
+
+ @Override
+ public ObjectAnimator clone() {
+ final ObjectAnimator anim = (ObjectAnimator) super.clone();
+ return anim;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ String returnVal = "ObjectAnimator@" + Integer.toHexString(hashCode()) + ", target " +
+ getTarget();
+ if (mValues != null) {
+ for (int i = 0; i < mValues.length; ++i) {
+ returnVal += "\n " + mValues[i].toString();
+ }
+ }
+ return returnVal;
+ }
+}
diff --git a/android-34/android/animation/PathKeyframes.java b/android-34/android/animation/PathKeyframes.java
new file mode 100644
index 0000000..b362904
--- /dev/null
+++ b/android-34/android/animation/PathKeyframes.java
@@ -0,0 +1,251 @@
+/*
+ * 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.animation;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+
+import java.util.ArrayList;
+
+/**
+ * PathKeyframes relies on approximating the Path as a series of line segments.
+ * The line segments are recursively divided until there is less than 1/2 pixel error
+ * between the lines and the curve. Each point of the line segment is converted
+ * to a Keyframe and a linear interpolation between Keyframes creates a good approximation
+ * of the curve.
+ * <p>
+ * PathKeyframes is optimized to reduce the number of objects created when there are
+ * many keyframes for a curve.
+ * </p>
+ * <p>
+ * Typically, the returned type is a PointF, but the individual components can be extracted
+ * as either an IntKeyframes or FloatKeyframes.
+ * </p>
+ * @hide
+ */
+public class PathKeyframes implements Keyframes {
+ private static final int FRACTION_OFFSET = 0;
+ private static final int X_OFFSET = 1;
+ private static final int Y_OFFSET = 2;
+ private static final int NUM_COMPONENTS = 3;
+ private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>();
+
+ private PointF mTempPointF = new PointF();
+ private float[] mKeyframeData;
+
+ public PathKeyframes(Path path) {
+ this(path, 0.5f);
+ }
+
+ public PathKeyframes(Path path, float error) {
+ if (path == null || path.isEmpty()) {
+ throw new IllegalArgumentException("The path must not be null or empty");
+ }
+ mKeyframeData = path.approximate(error);
+ }
+
+ @Override
+ public ArrayList<Keyframe> getKeyframes() {
+ return EMPTY_KEYFRAMES;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ int numPoints = mKeyframeData.length / 3;
+ if (fraction < 0) {
+ return interpolateInRange(fraction, 0, 1);
+ } else if (fraction > 1) {
+ return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
+ } else if (fraction == 0) {
+ return pointForIndex(0);
+ } else if (fraction == 1) {
+ return pointForIndex(numPoints - 1);
+ } else {
+ // Binary search for the correct section
+ int low = 0;
+ int high = numPoints - 1;
+
+ while (low <= high) {
+ int mid = (low + high) / 2;
+ float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
+
+ if (fraction < midFraction) {
+ high = mid - 1;
+ } else if (fraction > midFraction) {
+ low = mid + 1;
+ } else {
+ return pointForIndex(mid);
+ }
+ }
+
+ // now high is below the fraction and low is above the fraction
+ return interpolateInRange(fraction, high, low);
+ }
+ }
+
+ private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
+ int startBase = (startIndex * NUM_COMPONENTS);
+ int endBase = (endIndex * NUM_COMPONENTS);
+
+ float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
+ float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
+
+ float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
+
+ float startX = mKeyframeData[startBase + X_OFFSET];
+ float endX = mKeyframeData[endBase + X_OFFSET];
+ float startY = mKeyframeData[startBase + Y_OFFSET];
+ float endY = mKeyframeData[endBase + Y_OFFSET];
+
+ float x = interpolate(intervalFraction, startX, endX);
+ float y = interpolate(intervalFraction, startY, endY);
+
+ mTempPointF.set(x, y);
+ return mTempPointF;
+ }
+
+ @Override
+ public void setEvaluator(TypeEvaluator evaluator) {
+ }
+
+ @Override
+ public Class getType() {
+ return PointF.class;
+ }
+
+ @Override
+ public Keyframes clone() {
+ Keyframes clone = null;
+ try {
+ clone = (Keyframes) super.clone();
+ } catch (CloneNotSupportedException e) {}
+ return clone;
+ }
+
+ private PointF pointForIndex(int index) {
+ int base = (index * NUM_COMPONENTS);
+ int xOffset = base + X_OFFSET;
+ int yOffset = base + Y_OFFSET;
+ mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
+ return mTempPointF;
+ }
+
+ private static float interpolate(float fraction, float startValue, float endValue) {
+ float diff = endValue - startValue;
+ return startValue + (diff * fraction);
+ }
+
+ /**
+ * Returns a FloatKeyframes for the X component of the Path.
+ * @return a FloatKeyframes for the X component of the Path.
+ */
+ public FloatKeyframes createXFloatKeyframes() {
+ return new FloatKeyframesBase() {
+ @Override
+ public float getFloatValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return pointF.x;
+ }
+ };
+ }
+
+ /**
+ * Returns a FloatKeyframes for the Y component of the Path.
+ * @return a FloatKeyframes for the Y component of the Path.
+ */
+ public FloatKeyframes createYFloatKeyframes() {
+ return new FloatKeyframesBase() {
+ @Override
+ public float getFloatValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return pointF.y;
+ }
+ };
+ }
+
+ /**
+ * Returns an IntKeyframes for the X component of the Path.
+ * @return an IntKeyframes for the X component of the Path.
+ */
+ public IntKeyframes createXIntKeyframes() {
+ return new IntKeyframesBase() {
+ @Override
+ public int getIntValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return Math.round(pointF.x);
+ }
+ };
+ }
+
+ /**
+ * Returns an IntKeyframeSet for the Y component of the Path.
+ * @return an IntKeyframeSet for the Y component of the Path.
+ */
+ public IntKeyframes createYIntKeyframes() {
+ return new IntKeyframesBase() {
+ @Override
+ public int getIntValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return Math.round(pointF.y);
+ }
+ };
+ }
+
+ private abstract static class SimpleKeyframes implements Keyframes {
+ @Override
+ public void setEvaluator(TypeEvaluator evaluator) {
+ }
+
+ @Override
+ public ArrayList<Keyframe> getKeyframes() {
+ return EMPTY_KEYFRAMES;
+ }
+
+ @Override
+ public Keyframes clone() {
+ Keyframes clone = null;
+ try {
+ clone = (Keyframes) super.clone();
+ } catch (CloneNotSupportedException e) {}
+ return clone;
+ }
+ }
+
+ abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
+ @Override
+ public Class getType() {
+ return Integer.class;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getIntValue(fraction);
+ }
+ }
+
+ abstract static class FloatKeyframesBase extends SimpleKeyframes
+ implements FloatKeyframes {
+ @Override
+ public Class getType() {
+ return Float.class;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getFloatValue(fraction);
+ }
+ }
+}
diff --git a/android-34/android/animation/PointFEvaluator.java b/android-34/android/animation/PointFEvaluator.java
new file mode 100644
index 0000000..91d501f
--- /dev/null
+++ b/android-34/android/animation/PointFEvaluator.java
@@ -0,0 +1,83 @@
+/*
+ * 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.animation;
+
+import android.graphics.PointF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>PointF</code> values.
+ */
+public class PointFEvaluator implements TypeEvaluator<PointF> {
+
+ /**
+ * When null, a new PointF is returned on every evaluate call. When non-null,
+ * mPoint will be modified and returned on every evaluate.
+ */
+ private PointF mPoint;
+
+ /**
+ * Construct a PointFEvaluator that returns a new PointF on every evaluate call.
+ * To avoid creating an object for each evaluate call,
+ * {@link PointFEvaluator#PointFEvaluator(android.graphics.PointF)} should be used
+ * whenever possible.
+ */
+ public PointFEvaluator() {
+ }
+
+ /**
+ * Constructs a PointFEvaluator that modifies and returns <code>reuse</code>
+ * in {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} calls.
+ * The value returned from
+ * {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} should
+ * not be cached because it will change over time as the object is reused on each
+ * call.
+ *
+ * @param reuse A PointF to be modified and returned by evaluate.
+ */
+ public PointFEvaluator(PointF reuse) {
+ mPoint = reuse;
+ }
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end PointF values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the PointF objects
+ * (x, y).
+ *
+ * <p>If {@link #PointFEvaluator(android.graphics.PointF)} was used to construct
+ * this PointFEvaluator, the object returned will be the <code>reuse</code>
+ * passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start PointF
+ * @param endValue The end PointF
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
+ float x = startValue.x + (fraction * (endValue.x - startValue.x));
+ float y = startValue.y + (fraction * (endValue.y - startValue.y));
+
+ if (mPoint != null) {
+ mPoint.set(x, y);
+ return mPoint;
+ } else {
+ return new PointF(x, y);
+ }
+ }
+}
diff --git a/android-34/android/animation/PropertyValuesHolder.java b/android-34/android/animation/PropertyValuesHolder.java
new file mode 100644
index 0000000..76806a2
--- /dev/null
+++ b/android-34/android/animation/PropertyValuesHolder.java
@@ -0,0 +1,1729 @@
+/*
+ * 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.animation;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatProperty;
+import android.util.IntProperty;
+import android.util.Log;
+import android.util.PathParser;
+import android.util.Property;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class holds information about a property and the values that that property
+ * should take on during an animation. PropertyValuesHolder objects can be used to create
+ * animations with ValueAnimator or ObjectAnimator that operate on several different properties
+ * in parallel.
+ */
+public class PropertyValuesHolder implements Cloneable {
+
+ /**
+ * The name of the property associated with the values. This need not be a real property,
+ * unless this object is being used with ObjectAnimator. But this is the name by which
+ * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
+ */
+ String mPropertyName;
+
+ /**
+ * @hide
+ */
+ protected Property mProperty;
+
+ /**
+ * The setter function, if needed. ObjectAnimator hands off this functionality to
+ * PropertyValuesHolder, since it holds all of the per-property information. This
+ * property is automatically
+ * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+ */
+ Method mSetter = null;
+
+ /**
+ * The getter function, if needed. ObjectAnimator hands off this functionality to
+ * PropertyValuesHolder, since it holds all of the per-property information. This
+ * property is automatically
+ * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+ * The getter is only derived and used if one of the values is null.
+ */
+ private Method mGetter = null;
+
+ /**
+ * The type of values supplied. This information is used both in deriving the setter/getter
+ * functions and in deriving the type of TypeEvaluator.
+ */
+ Class mValueType;
+
+ /**
+ * The set of keyframes (time/value pairs) that define this animation.
+ */
+ Keyframes mKeyframes = null;
+
+
+ // type evaluators for the primitive types handled by this implementation
+ private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
+ private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
+
+ // We try several different types when searching for appropriate setter/getter functions.
+ // The caller may have supplied values in a type that does not match the setter/getter
+ // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+ // Also, the use of generics in constructors means that we end up with the Object versions
+ // of primitive types (Float vs. float). But most likely, the setter/getter functions
+ // will take primitive types instead.
+ // So we supply an ordered array of other types to try before giving up.
+ private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+ Double.class, Integer.class};
+ private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+ Float.class, Double.class};
+ private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
+ Float.class, Integer.class};
+
+ // These maps hold all property entries for a particular class. This map
+ // is used to speed up property/setter/getter lookups for a given class/property
+ // combination. No need to use reflection on the combination more than once.
+ private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap =
+ new HashMap<Class, HashMap<String, Method>>();
+ private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
+ new HashMap<Class, HashMap<String, Method>>();
+
+ // Used to pass single value to varargs parameter in setter invocation
+ final Object[] mTmpValueArray = new Object[1];
+
+ /**
+ * The type evaluator used to calculate the animated values. This evaluator is determined
+ * automatically based on the type of the start/end objects passed into the constructor,
+ * but the system only knows about the primitive types int and float. Any other
+ * type will need to set the evaluator to a custom evaluator for that type.
+ */
+ private TypeEvaluator mEvaluator;
+
+ /**
+ * The value most recently calculated by calculateValue(). This is set during
+ * that function and might be retrieved later either by ValueAnimator.animatedValue() or
+ * by the property-setting logic in ObjectAnimator.animatedValue().
+ */
+ private Object mAnimatedValue;
+
+ /**
+ * Converts from the source Object type to the setter Object type.
+ */
+ private TypeConverter mConverter;
+
+ /**
+ * Internal utility constructor, used by the factory methods to set the property name.
+ * @param propertyName The name of the property for this holder.
+ */
+ private PropertyValuesHolder(String propertyName) {
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * Internal utility constructor, used by the factory methods to set the property.
+ * @param property The property for this holder.
+ */
+ private PropertyValuesHolder(Property property) {
+ mProperty = property;
+ if (property != null) {
+ mPropertyName = property.getName();
+ }
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of int values.
+ * @param propertyName The name of the property being animated.
+ * @param values The values that the named property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofInt(String propertyName, int... values) {
+ return new IntPropertyValuesHolder(propertyName, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of int values.
+ * @param property The property being animated. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
+ return new IntPropertyValuesHolder(property, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of <code>int[]</code> values. At least two <code>int[]</code> values must be supplied,
+ * a start and end value. If more values are supplied, the values will be animated from the
+ * start, through all intermediate values to the end value. When used with ObjectAnimator,
+ * the elements of the array represent the parameters of the setter function.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see IntArrayEvaluator#IntArrayEvaluator(int[])
+ * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static PropertyValuesHolder ofMultiInt(String propertyName, int[][] values) {
+ if (values.length < 2) {
+ throw new IllegalArgumentException("At least 2 values must be supplied");
+ }
+ int numParameters = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("values must not be null");
+ }
+ int length = values[i].length;
+ if (i == 0) {
+ numParameters = length;
+ } else if (length != numParameters) {
+ throw new IllegalArgumentException("Values must all have the same length");
+ }
+ }
+ IntArrayEvaluator evaluator = new IntArrayEvaluator(new int[numParameters]);
+ return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name to use
+ * as a multi-int setter. The values are animated along the path, with the first
+ * parameter of the setter set to the x coordinate and the second set to the y coordinate.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * The setter must take exactly two <code>int</code> parameters.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) {
+ Keyframes keyframes = KeyframeSet.ofPath(path);
+ PointFToIntArray converter = new PointFToIntArray();
+ return new MultiIntValuesHolder(propertyName, converter, null, keyframes);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values for use with ObjectAnimator multi-value setters. The Object
+ * values are converted to <code>int[]</code> using the converter.
+ *
+ * @param propertyName The property being animated or complete name of the setter.
+ * Should not be null.
+ * @param converter Used to convert the animated value to setter parameters.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ @SafeVarargs
+ public static <V> PropertyValuesHolder ofMultiInt(String propertyName,
+ TypeConverter<V, int[]> converter, TypeEvaluator<V> evaluator, V... values) {
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name or
+ * setter name for use in a multi-int setter function using ObjectAnimator. The values can be
+ * of any type, but the type should be consistent so that the supplied
+ * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
+ * <code>converter</code> converts the values to parameters in the setter function.
+ *
+ * <p>At least two values must be supplied, a start and an end value.</p>
+ *
+ * @param propertyName The name of the property to associate with the set of values. This
+ * may also be the complete name of a setter function.
+ * @param converter Converts <code>values</code> into int parameters for the setter.
+ * Can be null if the Keyframes have int[] values.
+ * @param evaluator Used to interpolate between values.
+ * @param values The values at specific fractional times to evaluate between
+ * @return A PropertyValuesHolder for a multi-int parameter setter.
+ */
+ public static <T> PropertyValuesHolder ofMultiInt(String propertyName,
+ TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of float values.
+ * @param propertyName The name of the property being animated.
+ * @param values The values that the named property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
+ return new FloatPropertyValuesHolder(propertyName, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of float values.
+ * @param property The property being animated. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
+ return new FloatPropertyValuesHolder(property, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of <code>float[]</code> values. At least two <code>float[]</code> values must be supplied,
+ * a start and end value. If more values are supplied, the values will be animated from the
+ * start, through all intermediate values to the end value. When used with ObjectAnimator,
+ * the elements of the array represent the parameters of the setter function.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see FloatArrayEvaluator#FloatArrayEvaluator(float[])
+ * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static PropertyValuesHolder ofMultiFloat(String propertyName, float[][] values) {
+ if (values.length < 2) {
+ throw new IllegalArgumentException("At least 2 values must be supplied");
+ }
+ int numParameters = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("values must not be null");
+ }
+ int length = values[i].length;
+ if (i == 0) {
+ numParameters = length;
+ } else if (length != numParameters) {
+ throw new IllegalArgumentException("Values must all have the same length");
+ }
+ }
+ FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[numParameters]);
+ return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name to use
+ * as a multi-float setter. The values are animated along the path, with the first
+ * parameter of the setter set to the x coordinate and the second set to the y coordinate.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * The setter must take exactly two <code>float</code> parameters.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) {
+ Keyframes keyframes = KeyframeSet.ofPath(path);
+ PointFToFloatArray converter = new PointFToFloatArray();
+ return new MultiFloatValuesHolder(propertyName, converter, null, keyframes);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values for use with ObjectAnimator multi-value setters. The Object
+ * values are converted to <code>float[]</code> using the converter.
+ *
+ * @param propertyName The property being animated or complete name of the setter.
+ * Should not be null.
+ * @param converter Used to convert the animated value to setter parameters.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ @SafeVarargs
+ public static <V> PropertyValuesHolder ofMultiFloat(String propertyName,
+ TypeConverter<V, float[]> converter, TypeEvaluator<V> evaluator, V... values) {
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name or
+ * setter name for use in a multi-float setter function using ObjectAnimator. The values can be
+ * of any type, but the type should be consistent so that the supplied
+ * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
+ * <code>converter</code> converts the values to parameters in the setter function.
+ *
+ * <p>At least two values must be supplied, a start and an end value.</p>
+ *
+ * @param propertyName The name of the property to associate with the set of values. This
+ * may also be the complete name of a setter function.
+ * @param converter Converts <code>values</code> into float parameters for the setter.
+ * Can be null if the Keyframes have float[] values.
+ * @param evaluator Used to interpolate between values.
+ * @param values The values at specific fractional times to evaluate between
+ * @return A PropertyValuesHolder for a multi-float parameter setter.
+ */
+ public static <T> PropertyValuesHolder ofMultiFloat(String propertyName,
+ TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param propertyName The name of the property being animated.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the named property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
+ Object... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * a Path along which the values should be animated. This variant supports a
+ * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+ * type.
+ *
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
+ * @param propertyName The name of the property being animated.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofObject(String propertyName,
+ TypeConverter<PointF, ?> converter, Path path) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.mKeyframes = KeyframeSet.ofPath(path);
+ pvh.mValueType = PointF.class;
+ pvh.setConverter(converter);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ @SafeVarargs
+ public static <V> PropertyValuesHolder ofObject(Property property,
+ TypeEvaluator<V> evaluator, V... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type. This variant also
+ * takes a <code>TypeConverter</code> to convert from animated values to the type
+ * of the property. If only one value is supplied, the <code>TypeConverter</code>
+ * must be a {@link android.animation.BidirectionalTypeConverter} to retrieve the current
+ * value.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts the animated object to the Property type.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see #setConverter(TypeConverter)
+ * @see TypeConverter
+ */
+ @SafeVarargs
+ public static <T, V> PropertyValuesHolder ofObject(Property<?, V> property,
+ TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.setConverter(converter);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * a Path along which the values should be animated. This variant supports a
+ * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+ * type.
+ *
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static <V> PropertyValuesHolder ofObject(Property<?, V> property,
+ TypeConverter<PointF, V> converter, Path path) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.mKeyframes = KeyframeSet.ofPath(path);
+ pvh.mValueType = PointF.class;
+ pvh.setConverter(converter);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name and set
+ * of values. These values can be of any type, but the type should be consistent so that
+ * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+ * the common type.
+ * <p>If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ * @param propertyName The name of the property associated with this set of values. This
+ * can be the actual property name to be used when using a ObjectAnimator object, or
+ * just a name used to get animated values, such as if this object is used with an
+ * ValueAnimator object.
+ * @param values The set of values to animate between.
+ */
+ public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return ofKeyframes(propertyName, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property and set
+ * of values. These values can be of any type, but the type should be consistent so that
+ * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+ * the common type.
+ * <p>If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling the property's
+ * {@link android.util.Property#get(Object)} function.
+ * Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction with
+ * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ * @param property The property associated with this set of values. Should not be null.
+ * @param values The set of values to animate between.
+ */
+ public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return ofKeyframes(property, keyframeSet);
+ }
+
+ static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
+ if (keyframes instanceof Keyframes.IntKeyframes) {
+ return new IntPropertyValuesHolder(propertyName, (Keyframes.IntKeyframes) keyframes);
+ } else if (keyframes instanceof Keyframes.FloatKeyframes) {
+ return new FloatPropertyValuesHolder(propertyName,
+ (Keyframes.FloatKeyframes) keyframes);
+ } else {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.mKeyframes = keyframes;
+ pvh.mValueType = keyframes.getType();
+ return pvh;
+ }
+ }
+
+ static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) {
+ if (keyframes instanceof Keyframes.IntKeyframes) {
+ return new IntPropertyValuesHolder(property, (Keyframes.IntKeyframes) keyframes);
+ } else if (keyframes instanceof Keyframes.FloatKeyframes) {
+ return new FloatPropertyValuesHolder(property, (Keyframes.FloatKeyframes) keyframes);
+ } else {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.mKeyframes = keyframes;
+ pvh.mValueType = keyframes.getType();
+ return pvh;
+ }
+ }
+
+ /**
+ * Set the animated values for this object to this set of ints.
+ * If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setIntValues(int... values) {
+ mValueType = int.class;
+ mKeyframes = KeyframeSet.ofInt(values);
+ }
+
+ /**
+ * Set the animated values for this object to this set of floats.
+ * If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setFloatValues(float... values) {
+ mValueType = float.class;
+ mKeyframes = KeyframeSet.ofFloat(values);
+ }
+
+ /**
+ * Set the animated values for this object to this set of Keyframes.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setKeyframes(Keyframe... values) {
+ int numKeyframes = values.length;
+ Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
+ mValueType = ((Keyframe)values[0]).getType();
+ for (int i = 0; i < numKeyframes; ++i) {
+ keyframes[i] = (Keyframe)values[i];
+ }
+ mKeyframes = new KeyframeSet(keyframes);
+ }
+
+ /**
+ * Set the animated values for this object to this set of Objects.
+ * If there is only one value, it is assumed to be the end value of an animation,
+ * and an initial value will be derived, if possible, by calling a getter function
+ * on the object. Also, if any value is null, the value will be filled in when the animation
+ * starts in the same way. This mechanism of automatically getting null values only works
+ * if the PropertyValuesHolder object is used in conjunction
+ * {@link ObjectAnimator}, and with a getter function
+ * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+ * no way of determining what the value should be.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
+ * after this method is called, callers should pass a copy of those objects instead.
+ *
+ * @param values One or more values that the animation will animate between.
+ */
+ public void setObjectValues(Object... values) {
+ mValueType = values[0].getClass();
+ mKeyframes = KeyframeSet.ofObject(values);
+ if (mEvaluator != null) {
+ mKeyframes.setEvaluator(mEvaluator);
+ }
+ }
+
+ /**
+ * Sets the converter to convert from the values type to the setter's parameter type.
+ * If only one value is supplied, <var>converter</var> must be a
+ * {@link android.animation.BidirectionalTypeConverter}.
+ * @param converter The converter to use to convert values.
+ */
+ public void setConverter(TypeConverter converter) {
+ mConverter = converter;
+ }
+
+ /**
+ * Determine the setter or getter function using the JavaBeans convention of setFoo or
+ * getFoo for a property named 'foo'. This function figures out what the name of the
+ * function should be and uses reflection to find the Method with that name on the
+ * target object.
+ *
+ * @param targetClass The class to search for the method
+ * @param prefix "set" or "get", depending on whether we need a setter or getter.
+ * @param valueType The type of the parameter (in the case of a setter). This type
+ * is derived from the values set on this PropertyValuesHolder. This type is used as
+ * a first guess at the parameter type, but we check for methods with several different
+ * types to avoid problems with slight mis-matches between supplied values and actual
+ * value types used on the setter.
+ * @return Method the method associated with mPropertyName.
+ */
+ private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
+ // TODO: faster implementation...
+ Method returnVal = null;
+ String methodName = getMethodName(prefix, mPropertyName);
+ Class args[] = null;
+ if (valueType == null) {
+ try {
+ returnVal = targetClass.getMethod(methodName, args);
+ } catch (NoSuchMethodException e) {
+ // Swallow the error, log it later
+ }
+ } else {
+ args = new Class[1];
+ Class typeVariants[];
+ if (valueType.equals(Float.class)) {
+ typeVariants = FLOAT_VARIANTS;
+ } else if (valueType.equals(Integer.class)) {
+ typeVariants = INTEGER_VARIANTS;
+ } else if (valueType.equals(Double.class)) {
+ typeVariants = DOUBLE_VARIANTS;
+ } else {
+ typeVariants = new Class[1];
+ typeVariants[0] = valueType;
+ }
+ for (Class typeVariant : typeVariants) {
+ args[0] = typeVariant;
+ try {
+ returnVal = targetClass.getMethod(methodName, args);
+ if (mConverter == null) {
+ // change the value type to suit
+ mValueType = typeVariant;
+ }
+ return returnVal;
+ } catch (NoSuchMethodException e) {
+ // Swallow the error and keep trying other variants
+ }
+ }
+ // If we got here, then no appropriate function was found
+ }
+
+ if (returnVal == null) {
+ Log.w("PropertyValuesHolder", "Method " +
+ getMethodName(prefix, mPropertyName) + "() with type " + valueType +
+ " not found on target class " + targetClass);
+ }
+
+ return returnVal;
+ }
+
+
+ /**
+ * Returns the setter or getter requested. This utility function checks whether the
+ * requested method exists in the propertyMapMap cache. If not, it calls another
+ * utility function to request the Method from the targetClass directly.
+ * @param targetClass The Class on which the requested method should exist.
+ * @param propertyMapMap The cache of setters/getters derived so far.
+ * @param prefix "set" or "get", for the setter or getter.
+ * @param valueType The type of parameter passed into the method (null for getter).
+ * @return Method the method associated with mPropertyName.
+ */
+ private Method setupSetterOrGetter(Class targetClass,
+ HashMap<Class, HashMap<String, Method>> propertyMapMap,
+ String prefix, Class valueType) {
+ Method setterOrGetter = null;
+ synchronized(propertyMapMap) {
+ // Have to lock property map prior to reading it, to guard against
+ // another thread putting something in there after we've checked it
+ // but before we've added an entry to it
+ HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ setterOrGetter = propertyMap.get(mPropertyName);
+ }
+ }
+ if (!wasInMap) {
+ setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Method>();
+ propertyMapMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, setterOrGetter);
+ }
+ }
+ return setterOrGetter;
+ }
+
+ /**
+ * Utility function to get the setter from targetClass
+ * @param targetClass The Class on which the requested method should exist.
+ */
+ void setupSetter(Class targetClass) {
+ Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
+ mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
+ }
+
+ /**
+ * Utility function to get the getter from targetClass
+ */
+ private void setupGetter(Class targetClass) {
+ mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. If the setter has not been manually set for this
+ * object, it will be derived automatically given the property name, target object, and
+ * types of values supplied. If no getter has been set, it will be supplied iff any of the
+ * supplied values was null. If there is a null value, then the getter (supplied or derived)
+ * will be called to set those null values to the current value of the property
+ * on the target object.
+ * @param target The object on which the setter (and possibly getter) exist.
+ */
+ void setupSetterAndGetter(Object target) {
+ if (mProperty != null) {
+ // check to make sure that mProperty is on the class of target
+ try {
+ Object testValue = null;
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
+ if (!kf.hasValue() || kf.valueWasSetOnStart()) {
+ if (testValue == null) {
+ testValue = convertBack(mProperty.get(target));
+ }
+ kf.setValue(testValue);
+ kf.setValueWasSetOnStart(true);
+ }
+ }
+ return;
+ } catch (ClassCastException e) {
+ Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
+ ") on target object " + target + ". Trying reflection instead");
+ mProperty = null;
+ }
+ }
+ // We can't just say 'else' here because the catch statement sets mProperty to null.
+ if (mProperty == null) {
+ Class targetClass = target.getClass();
+ if (mSetter == null) {
+ setupSetter(targetClass);
+ }
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
+ if (!kf.hasValue() || kf.valueWasSetOnStart()) {
+ if (mGetter == null) {
+ setupGetter(targetClass);
+ if (mGetter == null) {
+ // Already logged the error - just return to avoid NPE
+ return;
+ }
+ }
+ try {
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
+ kf.setValueWasSetOnStart(true);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+ }
+ }
+
+ private Object convertBack(Object value) {
+ if (mConverter != null) {
+ if (!(mConverter instanceof BidirectionalTypeConverter)) {
+ throw new IllegalArgumentException("Converter "
+ + mConverter.getClass().getName()
+ + " must be a BidirectionalTypeConverter");
+ }
+ value = ((BidirectionalTypeConverter) mConverter).convertBack(value);
+ }
+ return value;
+ }
+
+ /**
+ * Utility function to set the value stored in a particular Keyframe. The value used is
+ * whatever the value is for the property name specified in the keyframe on the target object.
+ *
+ * @param target The target object from which the current value should be extracted.
+ * @param kf The keyframe which holds the property name and value.
+ */
+ private void setupValue(Object target, Keyframe kf) {
+ if (mProperty != null) {
+ Object value = convertBack(mProperty.get(target));
+ kf.setValue(value);
+ } else {
+ try {
+ if (mGetter == null) {
+ Class targetClass = target.getClass();
+ setupGetter(targetClass);
+ if (mGetter == null) {
+ // Already logged the error - just return to avoid NPE
+ return;
+ }
+ }
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ /**
+ * This function is called by ObjectAnimator when setting the start values for an animation.
+ * The start values are set according to the current values in the target object. The
+ * property whose value is extracted is whatever is specified by the propertyName of this
+ * PropertyValuesHolder object.
+ *
+ * @param target The object which holds the start values that should be set.
+ */
+ void setupStartValue(Object target) {
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ if (!keyframes.isEmpty()) {
+ setupValue(target, keyframes.get(0));
+ }
+ }
+
+ /**
+ * This function is called by ObjectAnimator when setting the end values for an animation.
+ * The end values are set according to the current values in the target object. The
+ * property whose value is extracted is whatever is specified by the propertyName of this
+ * PropertyValuesHolder object.
+ *
+ * @param target The object which holds the start values that should be set.
+ */
+ void setupEndValue(Object target) {
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
+ if (!keyframes.isEmpty()) {
+ setupValue(target, keyframes.get(keyframes.size() - 1));
+ }
+ }
+
+ @Override
+ public PropertyValuesHolder clone() {
+ try {
+ PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
+ newPVH.mPropertyName = mPropertyName;
+ newPVH.mProperty = mProperty;
+ newPVH.mKeyframes = mKeyframes.clone();
+ newPVH.mEvaluator = mEvaluator;
+ return newPVH;
+ } catch (CloneNotSupportedException e) {
+ // won't reach here
+ return null;
+ }
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ * @param target The target object on which the value is set
+ */
+ void setAnimatedValue(Object target) {
+ if (mProperty != null) {
+ mProperty.set(target, getAnimatedValue());
+ }
+ if (mSetter != null) {
+ try {
+ mTmpValueArray[0] = getAnimatedValue();
+ mSetter.invoke(target, mTmpValueArray);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ /**
+ * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
+ * to calculate animated values.
+ */
+ void init() {
+ if (mEvaluator == null) {
+ // We already handle int and float automatically, but not their Object
+ // equivalents
+ mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
+ (mValueType == Float.class) ? sFloatEvaluator :
+ null;
+ }
+ if (mEvaluator != null) {
+ // KeyframeSet knows how to evaluate the common types - only give it a custom
+ // evaluator if one has been set on this class
+ mKeyframes.setEvaluator(mEvaluator);
+ }
+ }
+
+ /**
+ * The TypeEvaluator will be automatically determined based on the type of values
+ * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
+ * desired. This may be important in cases where either the type of the values supplied
+ * do not match the way that they should be interpolated between, or if the values
+ * are of a custom type or one not currently understood by the animation system. Currently,
+ * only values of type float and int (and their Object equivalents: Float
+ * and Integer) are correctly interpolated; all other types require setting a TypeEvaluator.
+ * @param evaluator
+ */
+ public void setEvaluator(TypeEvaluator evaluator) {
+ mEvaluator = evaluator;
+ mKeyframes.setEvaluator(evaluator);
+ }
+
+ /**
+ * Function used to calculate the value according to the evaluator set up for
+ * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
+ *
+ * @param fraction The elapsed, interpolated fraction of the animation.
+ */
+ void calculateValue(float fraction) {
+ Object value = mKeyframes.getValue(fraction);
+ mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
+ }
+
+ /**
+ * Sets the name of the property that will be animated. This name is used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ *
+ * <p>Note that the setter function derived from this property name
+ * must take the same parameter type as the
+ * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+ * the setter function will fail.</p>
+ *
+ * @param propertyName The name of the property being animated.
+ */
+ public void setPropertyName(String propertyName) {
+ mPropertyName = propertyName;
+ }
+
+ /**
+ * Sets the property that will be animated.
+ *
+ * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
+ * must exist on the target object specified in that ObjectAnimator.</p>
+ *
+ * @param property The property being animated.
+ */
+ public void setProperty(Property property) {
+ mProperty = property;
+ }
+
+ /**
+ * Gets the name of the property that will be animated. This name will be used to derive
+ * a setter function that will be called to set animated values.
+ * For example, a property name of <code>foo</code> will result
+ * in a call to the function <code>setFoo()</code> on the target object. If either
+ * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+ * also be derived and called.
+ */
+ public String getPropertyName() {
+ return mPropertyName;
+ }
+
+ /**
+ * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
+ * most recently calculated in calculateValue().
+ * @return
+ */
+ Object getAnimatedValue() {
+ return mAnimatedValue;
+ }
+
+ /**
+ * PropertyValuesHolder is Animators use to hold internal animation related data.
+ * Therefore, in order to replicate the animation behavior, we need to get data out of
+ * PropertyValuesHolder.
+ * @hide
+ */
+ public void getPropertyValues(PropertyValues values) {
+ init();
+ values.propertyName = mPropertyName;
+ values.type = mValueType;
+ values.startValue = mKeyframes.getValue(0);
+ if (values.startValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue);
+ }
+ values.endValue = mKeyframes.getValue(1);
+ if (values.endValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue);
+ }
+ // TODO: We need a better way to get data out of keyframes.
+ if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase
+ || mKeyframes instanceof PathKeyframes.IntKeyframesBase
+ || (mKeyframes.getKeyframes() != null && mKeyframes.getKeyframes().size() > 2)) {
+ // When a pvh has more than 2 keyframes, that means there are intermediate values in
+ // addition to start/end values defined for animators. Another case where such
+ // intermediate values are defined is when animator has a path to animate along. In
+ // these cases, a data source is needed to capture these intermediate values.
+ values.dataSource = new PropertyValues.DataSource() {
+ @Override
+ public Object getValueAtFraction(float fraction) {
+ return mKeyframes.getValue(fraction);
+ }
+ };
+ } else {
+ values.dataSource = null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Class getValueType() {
+ return mValueType;
+ }
+
+ @Override
+ public String toString() {
+ return mPropertyName + ": " + mKeyframes.toString();
+ }
+
+ /**
+ * Utility method to derive a setter/getter method name from a property name, where the
+ * prefix is typically "set" or "get" and the first letter of the property name is
+ * capitalized.
+ *
+ * @param prefix The precursor to the method name, before the property name begins, typically
+ * "set" or "get".
+ * @param propertyName The name of the property that represents the bulk of the method name
+ * after the prefix. The first letter of this word will be capitalized in the resulting
+ * method name.
+ * @return String the property name converted to a method name according to the conventions
+ * specified above.
+ */
+ static String getMethodName(String prefix, String propertyName) {
+ if (propertyName == null || propertyName.length() == 0) {
+ // shouldn't get here
+ return prefix;
+ }
+ char firstLetter = Character.toUpperCase(propertyName.charAt(0));
+ String theRest = propertyName.substring(1);
+ return prefix + firstLetter + theRest;
+ }
+
+ static class IntPropertyValuesHolder extends PropertyValuesHolder {
+
+ // Cache JNI functions to avoid looking them up twice
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+ long mJniSetter;
+ private IntProperty mIntProperty;
+
+ Keyframes.IntKeyframes mIntKeyframes;
+ int mIntAnimatedValue;
+
+ public IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes) {
+ super(propertyName);
+ mValueType = int.class;
+ mKeyframes = keyframes;
+ mIntKeyframes = keyframes;
+ }
+
+ public IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes) {
+ super(property);
+ mValueType = int.class;
+ mKeyframes = keyframes;
+ mIntKeyframes = keyframes;
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) mProperty;
+ }
+ }
+
+ public IntPropertyValuesHolder(String propertyName, int... values) {
+ super(propertyName);
+ setIntValues(values);
+ }
+
+ public IntPropertyValuesHolder(Property property, int... values) {
+ super(property);
+ setIntValues(values);
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) mProperty;
+ }
+ }
+
+ @Override
+ public void setProperty(Property property) {
+ if (property instanceof IntProperty) {
+ mIntProperty = (IntProperty) property;
+ } else {
+ super.setProperty(property);
+ }
+ }
+
+ @Override
+ public void setIntValues(int... values) {
+ super.setIntValues(values);
+ mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
+ }
+
+ @Override
+ void calculateValue(float fraction) {
+ mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
+ }
+
+ @Override
+ Object getAnimatedValue() {
+ return mIntAnimatedValue;
+ }
+
+ @Override
+ public IntPropertyValuesHolder clone() {
+ IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
+ newPVH.mIntKeyframes = (Keyframes.IntKeyframes) newPVH.mKeyframes;
+ return newPVH;
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ if (mIntProperty != null) {
+ mIntProperty.setValue(target, mIntAnimatedValue);
+ return;
+ }
+ if (mProperty != null) {
+ mProperty.set(target, mIntAnimatedValue);
+ return;
+ }
+ if (mJniSetter != 0) {
+ nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
+ return;
+ }
+ if (mSetter != null) {
+ try {
+ mTmpValueArray[0] = mIntAnimatedValue;
+ mSetter.invoke(target, mTmpValueArray);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mProperty != null) {
+ return;
+ }
+ // Check new static hashmap<propName, int> for setter method
+ synchronized(sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ try {
+ mJniSetter = nGetIntMethod(targetClass, methodName);
+ } catch (NoSuchMethodError e) {
+ // Couldn't find it via JNI - try reflection next. Probably means the method
+ // doesn't exist, or the type is wrong. An error will be logged later if
+ // reflection fails as well.
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ if (mJniSetter == 0) {
+ // Couldn't find method through fast JNI approach - just use reflection
+ super.setupSetter(targetClass);
+ }
+ }
+ }
+
+ static class FloatPropertyValuesHolder extends PropertyValuesHolder {
+
+ // Cache JNI functions to avoid looking them up twice
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+ long mJniSetter;
+ private FloatProperty mFloatProperty;
+
+ Keyframes.FloatKeyframes mFloatKeyframes;
+ float mFloatAnimatedValue;
+
+ public FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes) {
+ super(propertyName);
+ mValueType = float.class;
+ mKeyframes = keyframes;
+ mFloatKeyframes = keyframes;
+ }
+
+ public FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes) {
+ super(property);
+ mValueType = float.class;
+ mKeyframes = keyframes;
+ mFloatKeyframes = keyframes;
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) mProperty;
+ }
+ }
+
+ public FloatPropertyValuesHolder(String propertyName, float... values) {
+ super(propertyName);
+ setFloatValues(values);
+ }
+
+ public FloatPropertyValuesHolder(Property property, float... values) {
+ super(property);
+ setFloatValues(values);
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) mProperty;
+ }
+ }
+
+ @Override
+ public void setProperty(Property property) {
+ if (property instanceof FloatProperty) {
+ mFloatProperty = (FloatProperty) property;
+ } else {
+ super.setProperty(property);
+ }
+ }
+
+ @Override
+ public void setFloatValues(float... values) {
+ super.setFloatValues(values);
+ mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
+ }
+
+ @Override
+ void calculateValue(float fraction) {
+ mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
+ }
+
+ @Override
+ Object getAnimatedValue() {
+ return mFloatAnimatedValue;
+ }
+
+ @Override
+ public FloatPropertyValuesHolder clone() {
+ FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
+ newPVH.mFloatKeyframes = (Keyframes.FloatKeyframes) newPVH.mKeyframes;
+ return newPVH;
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ if (mFloatProperty != null) {
+ mFloatProperty.setValue(target, mFloatAnimatedValue);
+ return;
+ }
+ if (mProperty != null) {
+ mProperty.set(target, mFloatAnimatedValue);
+ return;
+ }
+ if (mJniSetter != 0) {
+ nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
+ return;
+ }
+ if (mSetter != null) {
+ try {
+ mTmpValueArray[0] = mFloatAnimatedValue;
+ mSetter.invoke(target, mTmpValueArray);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ }
+ }
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mProperty != null) {
+ return;
+ }
+ // Check new static hashmap<propName, int> for setter method
+ synchronized (sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ try {
+ mJniSetter = nGetFloatMethod(targetClass, methodName);
+ } catch (NoSuchMethodError e) {
+ // Couldn't find it via JNI - try reflection next. Probably means the method
+ // doesn't exist, or the type is wrong. An error will be logged later if
+ // reflection fails as well.
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ if (mJniSetter == 0) {
+ // Couldn't find method through fast JNI approach - just use reflection
+ super.setupSetter(targetClass);
+ }
+ }
+
+ }
+
+ static class MultiFloatValuesHolder extends PropertyValuesHolder {
+ private long mJniSetter;
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+
+ public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Object... values) {
+ super(propertyName);
+ setConverter(converter);
+ setObjectValues(values);
+ setEvaluator(evaluator);
+ }
+
+ public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Keyframes keyframes) {
+ super(propertyName);
+ setConverter(converter);
+ mKeyframes = keyframes;
+ setEvaluator(evaluator);
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ *
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ float[] values = (float[]) getAnimatedValue();
+ int numParameters = values.length;
+ if (mJniSetter != 0) {
+ switch (numParameters) {
+ case 1:
+ nCallFloatMethod(target, mJniSetter, values[0]);
+ break;
+ case 2:
+ nCallTwoFloatMethod(target, mJniSetter, values[0], values[1]);
+ break;
+ case 4:
+ nCallFourFloatMethod(target, mJniSetter, values[0], values[1],
+ values[2], values[3]);
+ break;
+ default: {
+ nCallMultipleFloatMethod(target, mJniSetter, values);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. No getter can be used for multiple parameters.
+ *
+ * @param target The object on which the setter exists.
+ */
+ @Override
+ void setupSetterAndGetter(Object target) {
+ setupSetter(target.getClass());
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mJniSetter != 0) {
+ return;
+ }
+ synchronized(sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ calculateValue(0f);
+ float[] values = (float[]) getAnimatedValue();
+ int numParams = values.length;
+ try {
+ mJniSetter = nGetMultipleFloatMethod(targetClass, methodName, numParams);
+ } catch (NoSuchMethodError e) {
+ // try without the 'set' prefix
+ try {
+ mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName,
+ numParams);
+ } catch (NoSuchMethodError e2) {
+ // just try reflection next
+ }
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ }
+ }
+
+ static class MultiIntValuesHolder extends PropertyValuesHolder {
+ private long mJniSetter;
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+
+ public MultiIntValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Object... values) {
+ super(propertyName);
+ setConverter(converter);
+ setObjectValues(values);
+ setEvaluator(evaluator);
+ }
+
+ public MultiIntValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Keyframes keyframes) {
+ super(propertyName);
+ setConverter(converter);
+ mKeyframes = keyframes;
+ setEvaluator(evaluator);
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ *
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ int[] values = (int[]) getAnimatedValue();
+ int numParameters = values.length;
+ if (mJniSetter != 0) {
+ switch (numParameters) {
+ case 1:
+ nCallIntMethod(target, mJniSetter, values[0]);
+ break;
+ case 2:
+ nCallTwoIntMethod(target, mJniSetter, values[0], values[1]);
+ break;
+ case 4:
+ nCallFourIntMethod(target, mJniSetter, values[0], values[1],
+ values[2], values[3]);
+ break;
+ default: {
+ nCallMultipleIntMethod(target, mJniSetter, values);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. No getter can be used for multiple parameters.
+ *
+ * @param target The object on which the setter exists.
+ */
+ @Override
+ void setupSetterAndGetter(Object target) {
+ setupSetter(target.getClass());
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mJniSetter != 0) {
+ return;
+ }
+ synchronized(sJNISetterPropertyMap) {
+ HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ boolean wasInMap = false;
+ if (propertyMap != null) {
+ wasInMap = propertyMap.containsKey(mPropertyName);
+ if (wasInMap) {
+ Long jniSetter = propertyMap.get(mPropertyName);
+ if (jniSetter != null) {
+ mJniSetter = jniSetter;
+ }
+ }
+ }
+ if (!wasInMap) {
+ String methodName = getMethodName("set", mPropertyName);
+ calculateValue(0f);
+ int[] values = (int[]) getAnimatedValue();
+ int numParams = values.length;
+ try {
+ mJniSetter = nGetMultipleIntMethod(targetClass, methodName, numParams);
+ } catch (NoSuchMethodError e) {
+ // try without the 'set' prefix
+ try {
+ mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName,
+ numParams);
+ } catch (NoSuchMethodError e2) {
+ // couldn't find it.
+ }
+ }
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert from PointF to float[] for multi-float setters along a Path.
+ */
+ private static class PointFToFloatArray extends TypeConverter<PointF, float[]> {
+ private float[] mCoordinates = new float[2];
+
+ public PointFToFloatArray() {
+ super(PointF.class, float[].class);
+ }
+
+ @Override
+ public float[] convert(PointF value) {
+ mCoordinates[0] = value.x;
+ mCoordinates[1] = value.y;
+ return mCoordinates;
+ }
+ };
+
+ /**
+ * Convert from PointF to int[] for multi-int setters along a Path.
+ */
+ private static class PointFToIntArray extends TypeConverter<PointF, int[]> {
+ private int[] mCoordinates = new int[2];
+
+ public PointFToIntArray() {
+ super(PointF.class, int[].class);
+ }
+
+ @Override
+ public int[] convert(PointF value) {
+ mCoordinates[0] = Math.round(value.x);
+ mCoordinates[1] = Math.round(value.y);
+ return mCoordinates;
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static class PropertyValues {
+ public String propertyName;
+ public Class type;
+ public Object startValue;
+ public Object endValue;
+ public DataSource dataSource = null;
+ public interface DataSource {
+ Object getValueAtFraction(float fraction);
+ }
+ public String toString() {
+ return ("property name: " + propertyName + ", type: " + type + ", startValue: "
+ + startValue.toString() + ", endValue: " + endValue.toString());
+ }
+ }
+
+ native static private long nGetIntMethod(Class targetClass, String methodName);
+ native static private long nGetFloatMethod(Class targetClass, String methodName);
+ native static private long nGetMultipleIntMethod(Class targetClass, String methodName,
+ int numParams);
+ native static private long nGetMultipleFloatMethod(Class targetClass, String methodName,
+ int numParams);
+ native static private void nCallIntMethod(Object target, long methodID, int arg);
+ native static private void nCallFloatMethod(Object target, long methodID, float arg);
+ native static private void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2);
+ native static private void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2,
+ int arg3, int arg4);
+ native static private void nCallMultipleIntMethod(Object target, long methodID, int[] args);
+ native static private void nCallTwoFloatMethod(Object target, long methodID, float arg1,
+ float arg2);
+ native static private void nCallFourFloatMethod(Object target, long methodID, float arg1,
+ float arg2, float arg3, float arg4);
+ native static private void nCallMultipleFloatMethod(Object target, long methodID, float[] args);
+}
diff --git a/android-34/android/animation/RectEvaluator.java b/android-34/android/animation/RectEvaluator.java
new file mode 100644
index 0000000..23eb766
--- /dev/null
+++ b/android-34/android/animation/RectEvaluator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.animation;
+
+import android.graphics.Rect;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
+ */
+public class RectEvaluator implements TypeEvaluator<Rect> {
+
+ /**
+ * When null, a new Rect is returned on every evaluate call. When non-null,
+ * mRect will be modified and returned on every evaluate.
+ */
+ private Rect mRect;
+
+ /**
+ * Construct a RectEvaluator that returns a new Rect on every evaluate call.
+ * To avoid creating an object for each evaluate call,
+ * {@link RectEvaluator#RectEvaluator(android.graphics.Rect)} should be used
+ * whenever possible.
+ */
+ public RectEvaluator() {
+ }
+
+ /**
+ * Constructs a RectEvaluator that modifies and returns <code>reuseRect</code>
+ * in {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} calls.
+ * The value returned from
+ * {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} should
+ * not be cached because it will change over time as the object is reused on each
+ * call.
+ *
+ * @param reuseRect A Rect to be modified and returned by evaluate.
+ */
+ public RectEvaluator(Rect reuseRect) {
+ mRect = reuseRect;
+ }
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end Rect values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the Rect objects
+ * (left, top, right, and bottom).
+ *
+ * <p>If {@link #RectEvaluator(android.graphics.Rect)} was used to construct
+ * this RectEvaluator, the object returned will be the <code>reuseRect</code>
+ * passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start Rect
+ * @param endValue The end Rect
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+ int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
+ int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
+ int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
+ int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
+ if (mRect == null) {
+ return new Rect(left, top, right, bottom);
+ } else {
+ mRect.set(left, top, right, bottom);
+ return mRect;
+ }
+ }
+}
diff --git a/android-34/android/animation/RevealAnimator.java b/android-34/android/animation/RevealAnimator.java
new file mode 100644
index 0000000..0f85f49
--- /dev/null
+++ b/android-34/android/animation/RevealAnimator.java
@@ -0,0 +1,46 @@
+/*
+ * 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.animation;
+
+import android.view.RenderNodeAnimator;
+import android.view.View;
+
+/**
+ * Reveals a View with an animated clipping circle.
+ * The clipping is implemented efficiently by talking to a private reveal API on View.
+ * This hidden class currently only accessed by the {@link android.view.View}.
+ *
+ * @hide
+ */
+public class RevealAnimator extends RenderNodeAnimator {
+
+ private View mClipView;
+
+ public RevealAnimator(View clipView, int x, int y,
+ float startRadius, float endRadius) {
+ super(x, y, startRadius, endRadius);
+ mClipView = clipView;
+ setTarget(mClipView);
+ }
+
+ @Override
+ protected void onFinished() {
+ mClipView.setRevealClip(false, 0, 0, 0);
+ super.onFinished();
+ }
+
+}
diff --git a/android-34/android/animation/StateListAnimator.java b/android-34/android/animation/StateListAnimator.java
new file mode 100644
index 0000000..b6d6910
--- /dev/null
+++ b/android-34/android/animation/StateListAnimator.java
@@ -0,0 +1,333 @@
+/*
+ * 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.animation;
+
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConstantState;
+import android.util.StateSet;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Lets you define a number of Animators that will run on the attached View depending on the View's
+ * drawable state.
+ * <p>
+ * It can be defined in an XML file with the <code><selector></code> element.
+ * Each State Animator is defined in a nested <code><item></code> element.
+ *
+ * @attr ref android.R.styleable#DrawableStates_state_focused
+ * @attr ref android.R.styleable#DrawableStates_state_window_focused
+ * @attr ref android.R.styleable#DrawableStates_state_enabled
+ * @attr ref android.R.styleable#DrawableStates_state_checkable
+ * @attr ref android.R.styleable#DrawableStates_state_checked
+ * @attr ref android.R.styleable#DrawableStates_state_selected
+ * @attr ref android.R.styleable#DrawableStates_state_activated
+ * @attr ref android.R.styleable#DrawableStates_state_active
+ * @attr ref android.R.styleable#DrawableStates_state_single
+ * @attr ref android.R.styleable#DrawableStates_state_first
+ * @attr ref android.R.styleable#DrawableStates_state_middle
+ * @attr ref android.R.styleable#DrawableStates_state_last
+ * @attr ref android.R.styleable#DrawableStates_state_pressed
+ * @attr ref android.R.styleable#StateListAnimatorItem_animation
+ */
+public class StateListAnimator implements Cloneable {
+
+ private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
+ private Tuple mLastMatch = null;
+ private Animator mRunningAnimator = null;
+ private WeakReference<View> mViewRef;
+ private StateListAnimatorConstantState mConstantState;
+ private AnimatorListenerAdapter mAnimatorListener;
+ private @Config int mChangingConfigurations;
+
+ public StateListAnimator() {
+ initAnimatorListener();
+ }
+
+ private void initAnimatorListener() {
+ mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animation.setTarget(null);
+ if (mRunningAnimator == animation) {
+ mRunningAnimator = null;
+ }
+ }
+ };
+ }
+
+ /**
+ * Associates the given animator with the provided drawable state specs so that it will be run
+ * when the View's drawable state matches the specs.
+ *
+ * @param specs The drawable state specs to match against
+ * @param animator The animator to run when the specs match
+ */
+ public void addState(int[] specs, Animator animator) {
+ Tuple tuple = new Tuple(specs, animator);
+ tuple.mAnimator.addListener(mAnimatorListener);
+ mTuples.add(tuple);
+ mChangingConfigurations |= animator.getChangingConfigurations();
+ }
+
+ /**
+ * Returns the current {@link android.animation.Animator} which is started because of a state
+ * change.
+ *
+ * @return The currently running Animator or null if no Animator is running
+ * @hide
+ */
+ public Animator getRunningAnimator() {
+ return mRunningAnimator;
+ }
+
+ /**
+ * @hide
+ */
+ public View getTarget() {
+ return mViewRef == null ? null : mViewRef.get();
+ }
+
+ /**
+ * Called by View
+ * @hide
+ */
+ public void setTarget(View view) {
+ final View current = getTarget();
+ if (current == view) {
+ return;
+ }
+ if (current != null) {
+ clearTarget();
+ }
+ if (view != null) {
+ mViewRef = new WeakReference<View>(view);
+ }
+
+ }
+
+ private void clearTarget() {
+ final int size = mTuples.size();
+ for (int i = 0; i < size; i++) {
+ mTuples.get(i).mAnimator.setTarget(null);
+ }
+ mViewRef = null;
+ mLastMatch = null;
+ mRunningAnimator = null;
+ }
+
+ @Override
+ public StateListAnimator clone() {
+ try {
+ StateListAnimator clone = (StateListAnimator) super.clone();
+ clone.mTuples = new ArrayList<Tuple>(mTuples.size());
+ clone.mLastMatch = null;
+ clone.mRunningAnimator = null;
+ clone.mViewRef = null;
+ clone.mAnimatorListener = null;
+ clone.initAnimatorListener();
+ final int tupleSize = mTuples.size();
+ for (int i = 0; i < tupleSize; i++) {
+ final Tuple tuple = mTuples.get(i);
+ final Animator animatorClone = tuple.mAnimator.clone();
+ animatorClone.removeListener(mAnimatorListener);
+ clone.addState(tuple.mSpecs, animatorClone);
+ }
+ clone.setChangingConfigurations(getChangingConfigurations());
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError("cannot clone state list animator", e);
+ }
+ }
+
+ /**
+ * Called by View
+ * @hide
+ */
+ public void setState(int[] state) {
+ Tuple match = null;
+ final int count = mTuples.size();
+ for (int i = 0; i < count; i++) {
+ final Tuple tuple = mTuples.get(i);
+ if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
+ match = tuple;
+ break;
+ }
+ }
+ if (match == mLastMatch) {
+ return;
+ }
+ if (mLastMatch != null) {
+ cancel();
+ }
+ mLastMatch = match;
+ if (match != null) {
+ start(match);
+ }
+ }
+
+ private void start(Tuple match) {
+ match.mAnimator.setTarget(getTarget());
+ mRunningAnimator = match.mAnimator;
+ mRunningAnimator.start();
+ }
+
+ private void cancel() {
+ if (mRunningAnimator != null) {
+ mRunningAnimator.cancel();
+ mRunningAnimator = null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<Tuple> getTuples() {
+ return mTuples;
+ }
+
+ /**
+ * If there is an animation running for a recent state change, ends it.
+ * <p>
+ * This causes the animation to assign the end value(s) to the View.
+ */
+ public void jumpToCurrentState() {
+ if (mRunningAnimator != null) {
+ mRunningAnimator.end();
+ }
+ }
+
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created. The default implementation returns whatever was provided through
+ * {@link #setChangingConfigurations(int)} or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public @Config int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be recreated from resources instead of being cloned.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(@Config int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(@Config int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed. Default
+ * implementation creates a new {@link StateListAnimatorConstantState}. You can override this
+ * method to provide your custom logic or return null if you don't want this animator to be
+ * cached.
+ *
+ * @return The {@link android.content.res.ConstantState} associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<StateListAnimator> createConstantState() {
+ return new StateListAnimatorConstantState(this);
+ }
+
+ /**
+ * @hide
+ */
+ public static class Tuple {
+
+ final int[] mSpecs;
+
+ final Animator mAnimator;
+
+ private Tuple(int[] specs, Animator animator) {
+ mSpecs = specs;
+ mAnimator = animator;
+ }
+
+ /**
+ * @hide
+ */
+ public int[] getSpecs() {
+ return mSpecs;
+ }
+
+ /**
+ * @hide
+ */
+ public Animator getAnimator() {
+ return mAnimator;
+ }
+ }
+
+ /**
+ * Creates a constant state which holds changing configurations information associated with the
+ * given Animator.
+ * <p>
+ * When new instance is called, default implementation clones the Animator.
+ */
+ private static class StateListAnimatorConstantState
+ extends ConstantState<StateListAnimator> {
+
+ final StateListAnimator mAnimator;
+
+ @Config int mChangingConf;
+
+ public StateListAnimatorConstantState(StateListAnimator animator) {
+ mAnimator = animator;
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public StateListAnimator newInstance() {
+ final StateListAnimator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
+}
diff --git a/android-34/android/animation/TimeAnimator.java b/android-34/android/animation/TimeAnimator.java
new file mode 100644
index 0000000..113a21f
--- /dev/null
+++ b/android-34/android/animation/TimeAnimator.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import android.view.animation.AnimationUtils;
+
+/**
+ * This class provides a simple callback mechanism to listeners that is synchronized with all
+ * other animators in the system. There is no duration, interpolation, or object value-setting
+ * with this Animator. Instead, it is simply started, after which it proceeds to send out events
+ * on every animation frame to its TimeListener (if set), with information about this animator,
+ * the total elapsed time, and the elapsed time since the previous animation frame.
+ */
+public class TimeAnimator extends ValueAnimator {
+
+ private TimeListener mListener;
+ private long mPreviousTime = -1;
+
+ @Override
+ public void start() {
+ mPreviousTime = -1;
+ super.start();
+ }
+
+ @Override
+ boolean animateBasedOnTime(long currentTime) {
+ if (mListener != null) {
+ long totalTime = currentTime - mStartTime;
+ long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime);
+ mPreviousTime = currentTime;
+ mListener.onTimeUpdate(this, totalTime, deltaTime);
+ }
+ return false;
+ }
+
+ @Override
+ public void setCurrentPlayTime(long playTime) {
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = Math.max(mStartTime, currentTime - playTime);
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
+ animateBasedOnTime(currentTime);
+ }
+
+ /**
+ * Sets a listener that is sent update events throughout the life of
+ * an animation.
+ *
+ * @param listener the listener to be set.
+ */
+ public void setTimeListener(TimeListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ void animateValue(float fraction) {
+ // Noop
+ }
+
+ @Override
+ void initAnimation() {
+ // noop
+ }
+
+ /**
+ * Implementors of this interface can set themselves as update listeners
+ * to a <code>TimeAnimator</code> instance to receive callbacks on every animation
+ * frame to receive the total time since the animator started and the delta time
+ * since the last frame. The first time the listener is called,
+ * deltaTime will be zero. The same is true for totalTime, unless the animator was
+ * set to a specific {@link ValueAnimator#setCurrentPlayTime(long) currentPlayTime}
+ * prior to starting.
+ */
+ public static interface TimeListener {
+ /**
+ * <p>Notifies listeners of the occurrence of another frame of the animation,
+ * along with information about the elapsed time.</p>
+ *
+ * @param animation The animator sending out the notification.
+ * @param totalTime The total time elapsed since the animator started, in milliseconds.
+ * @param deltaTime The time elapsed since the previous frame, in milliseconds.
+ */
+ void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime);
+
+ }
+}
diff --git a/android-34/android/animation/TimeInterpolator.java b/android-34/android/animation/TimeInterpolator.java
new file mode 100644
index 0000000..0f5d8bf
--- /dev/null
+++ b/android-34/android/animation/TimeInterpolator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.animation;
+
+/**
+ * A time interpolator defines the rate of change of an animation. This allows animations
+ * to have non-linear motion, such as acceleration and deceleration.
+ */
+public interface TimeInterpolator {
+
+ /**
+ * Maps a value representing the elapsed fraction of an animation to a value that represents
+ * the interpolated fraction. This interpolated value is then multiplied by the change in
+ * value of an animation to derive the animated value at the current elapsed animation time.
+ *
+ * @param input A value between 0 and 1.0 indicating our current point
+ * in the animation where 0 represents the start and 1.0 represents
+ * the end
+ * @return The interpolation value. This value can be more than 1.0 for
+ * interpolators which overshoot their targets, or less than 0 for
+ * interpolators that undershoot their targets.
+ */
+ float getInterpolation(float input);
+}
diff --git a/android-34/android/animation/TypeConverter.java b/android-34/android/animation/TypeConverter.java
new file mode 100644
index 0000000..9ead2ad
--- /dev/null
+++ b/android-34/android/animation/TypeConverter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.animation;
+
+/**
+ * Abstract base class used convert type T to another type V. This
+ * is necessary when the value types of in animation are different
+ * from the property type.
+ * @see PropertyValuesHolder#setConverter(TypeConverter)
+ */
+public abstract class TypeConverter<T, V> {
+ private Class<T> mFromClass;
+ private Class<V> mToClass;
+
+ public TypeConverter(Class<T> fromClass, Class<V> toClass) {
+ mFromClass = fromClass;
+ mToClass = toClass;
+ }
+
+ /**
+ * Returns the target converted type. Used by the animation system to determine
+ * the proper setter function to call.
+ * @return The Class to convert the input to.
+ */
+ Class<V> getTargetType() {
+ return mToClass;
+ }
+
+ /**
+ * Returns the source conversion type.
+ */
+ Class<T> getSourceType() {
+ return mFromClass;
+ }
+
+ /**
+ * Converts a value from one type to another.
+ * @param value The Object to convert.
+ * @return A value of type V, converted from <code>value</code>.
+ */
+ public abstract V convert(T value);
+}
diff --git a/android-34/android/animation/TypeEvaluator.java b/android-34/android/animation/TypeEvaluator.java
new file mode 100644
index 0000000..429c435
--- /dev/null
+++ b/android-34/android/animation/TypeEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * 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.animation;
+
+/**
+ * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators
+ * allow developers to create animations on arbitrary property types, by allowing them to supply
+ * custom evaluators for types that are not automatically understood and used by the animation
+ * system.
+ *
+ * @see ValueAnimator#setEvaluator(TypeEvaluator)
+ */
+public interface TypeEvaluator<T> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and end values, with
+ * <code>fraction</code> representing the proportion between the start and end values. The
+ * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
+ * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+ * and <code>t</code> is <code>fraction</code>.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ public T evaluate(float fraction, T startValue, T endValue);
+
+}
diff --git a/android-34/android/animation/ValueAnimator.java b/android-34/android/animation/ValueAnimator.java
new file mode 100644
index 0000000..ead238f
--- /dev/null
+++ b/android-34/android/animation/ValueAnimator.java
@@ -0,0 +1,1801 @@
+/*
+ * 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.animation;
+
+import android.annotation.CallSuper;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.AndroidRuntimeException;
+import android.util.Log;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.LinearInterpolator;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class provides a simple timing engine for running animations
+ * which calculate animated values and set them on target objects.
+ *
+ * <p>There is a single timing pulse that all animations use. It runs in a
+ * custom handler to ensure that property changes happen on the UI thread.</p>
+ *
+ * <p>By default, ValueAnimator uses non-linear time interpolation, via the
+ * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates
+ * out of an animation. This behavior can be changed by calling
+ * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p>
+ *
+ * <p>Animators can be created from either code or resource files. Here is an example
+ * of a ValueAnimator resource file:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/animator.xml ValueAnimatorResources}
+ *
+ * <p>Starting from API 23, it is also possible to use a combination of {@link PropertyValuesHolder}
+ * and {@link Keyframe} resource tags to create a multi-step animation.
+ * Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/value_animator_pvh_kf.xml
+ * ValueAnimatorKeyframeResources}
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about animating with {@code ValueAnimator}, read the
+ * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#value-animator">Property
+ * Animation</a> developer guide.</p>
+ * </div>
+ */
+@SuppressWarnings("unchecked")
+public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
+ private static final String TAG = "ValueAnimator";
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE_ANIMATION_FRACTION = SystemProperties.getBoolean(
+ "persist.debug.animator.trace_fraction", false);
+
+ /**
+ * Internal constants
+ */
+
+ /**
+ * System-wide animation scale.
+ *
+ * <p>To check whether animations are enabled system-wise use {@link #areAnimatorsEnabled()}.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private static float sDurationScale = 1.0f;
+
+ private static final ArrayList<WeakReference<DurationScaleChangeListener>>
+ sDurationScaleChangeListeners = new ArrayList<>();
+
+ /**
+ * Internal variables
+ * NOTE: This object implements the clone() method, making a deep copy of any referenced
+ * objects. As other non-trivial fields are added to this class, make sure to add logic
+ * to clone() to make deep copies of them.
+ */
+
+ /**
+ * The first time that the animation's animateFrame() method is called. This time is used to
+ * determine elapsed time (and therefore the elapsed fraction) in subsequent calls
+ * to animateFrame().
+ *
+ * Whenever mStartTime is set, you must also update mStartTimeCommitted.
+ */
+ long mStartTime = -1;
+
+ /**
+ * When true, the start time has been firmly committed as a chosen reference point in
+ * time by which the progress of the animation will be evaluated. When false, the
+ * start time may be updated when the first animation frame is committed so as
+ * to compensate for jank that may have occurred between when the start time was
+ * initialized and when the frame was actually drawn.
+ *
+ * This flag is generally set to false during the first frame of the animation
+ * when the animation playing state transitions from STOPPED to RUNNING or
+ * resumes after having been paused. This flag is set to true when the start time
+ * is firmly committed and should not be further compensated for jank.
+ */
+ boolean mStartTimeCommitted;
+
+ /**
+ * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
+ * to a value.
+ */
+ float mSeekFraction = -1;
+
+ /**
+ * Set on the next frame after pause() is called, used to calculate a new startTime
+ * or delayStartTime which allows the animator to continue from the point at which
+ * it was paused. If negative, has not yet been set.
+ */
+ private long mPauseTime;
+
+ /**
+ * Set when an animator is resumed. This triggers logic in the next frame which
+ * actually resumes the animator.
+ */
+ private boolean mResumed = false;
+
+ // The time interpolator to be used if none is set on the animation
+ private static final TimeInterpolator sDefaultInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ /**
+ * Flag to indicate whether this animator is playing in reverse mode, specifically
+ * by being started or interrupted by a call to reverse(). This flag is different than
+ * mPlayingBackwards, which indicates merely whether the current iteration of the
+ * animator is playing in reverse. It is used in corner cases to determine proper end
+ * behavior.
+ */
+ private boolean mReversing;
+
+ /**
+ * Tracks the overall fraction of the animation, ranging from 0 to mRepeatCount + 1
+ */
+ private float mOverallFraction = 0f;
+
+ /**
+ * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction().
+ * This is calculated by interpolating the fraction (range: [0, 1]) in the current iteration.
+ */
+ private float mCurrentFraction = 0f;
+
+ /**
+ * Tracks the time (in milliseconds) when the last frame arrived.
+ */
+ private long mLastFrameTime = -1;
+
+ /**
+ * Tracks the time (in milliseconds) when the first frame arrived. Note the frame may arrive
+ * during the start delay.
+ */
+ private long mFirstFrameTime = -1;
+
+ /**
+ * Additional playing state to indicate whether an animator has been start()'d. There is
+ * some lag between a call to start() and the first animation frame. We should still note
+ * that the animation has been started, even if it's first animation frame has not yet
+ * happened, and reflect that state in isRunning().
+ * Note that delayed animations are different: they are not started until their first
+ * animation frame, which occurs after their delay elapses.
+ */
+ private boolean mRunning = false;
+
+ /**
+ * Additional playing state to indicate whether an animator has been start()'d, whether or
+ * not there is a nonzero startDelay.
+ */
+ private boolean mStarted = false;
+
+ /**
+ * Flag that denotes whether the animation is set up and ready to go. Used to
+ * set up animation that has not yet been started.
+ */
+ boolean mInitialized = false;
+
+ /**
+ * Flag that tracks whether animation has been requested to end.
+ */
+ private boolean mAnimationEndRequested = false;
+
+ //
+ // Backing variables
+ //
+
+ // How long the animation should last in ms
+ @UnsupportedAppUsage
+ private long mDuration = 300;
+
+ // The amount of time in ms to delay starting the animation after start() is called. Note
+ // that this start delay is unscaled. When there is a duration scale set on the animator, the
+ // scaling factor will be applied to this delay.
+ private long mStartDelay = 0;
+
+ // The number of times the animation will repeat. The default is 0, which means the animation
+ // will play only once
+ private int mRepeatCount = 0;
+
+ /**
+ * The type of repetition that will occur when repeatMode is nonzero. RESTART means the
+ * animation will start from the beginning on every new cycle. REVERSE means the animation
+ * will reverse directions on each iteration.
+ */
+ private int mRepeatMode = RESTART;
+
+ /**
+ * Whether or not the animator should register for its own animation callback to receive
+ * animation pulse.
+ */
+ private boolean mSelfPulse = true;
+
+ /**
+ * Whether or not the animator has been requested to start without pulsing. This flag gets set
+ * in startWithoutPulsing(), and reset in start().
+ */
+ private boolean mSuppressSelfPulseRequested = false;
+
+ /**
+ * The time interpolator to be used. The elapsed fraction of the animation will be passed
+ * through this interpolator to calculate the interpolated fraction, which is then used to
+ * calculate the animated values.
+ */
+ private TimeInterpolator mInterpolator = sDefaultInterpolator;
+
+ /**
+ * The set of listeners to be sent events through the life of an animation.
+ */
+ ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+
+ /**
+ * The property/value sets being animated.
+ */
+ PropertyValuesHolder[] mValues;
+
+ /**
+ * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values
+ * by property name during calls to getAnimatedValue(String).
+ */
+ HashMap<String, PropertyValuesHolder> mValuesMap;
+
+ /**
+ * If set to non-negative value, this will override {@link #sDurationScale}.
+ */
+ private float mDurationScale = -1f;
+
+ /**
+ * Animation handler used to schedule updates for this animation.
+ */
+ private AnimationHandler mAnimationHandler;
+
+ /**
+ * Public constants
+ */
+
+ /** @hide */
+ @IntDef({RESTART, REVERSE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RepeatMode {}
+
+ /**
+ * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+ * or a positive value, the animation restarts from the beginning.
+ */
+ public static final int RESTART = 1;
+ /**
+ * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+ * or a positive value, the animation reverses direction on every iteration.
+ */
+ public static final int REVERSE = 2;
+ /**
+ * This value used used with the {@link #setRepeatCount(int)} property to repeat
+ * the animation indefinitely.
+ */
+ public static final int INFINITE = -1;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ @MainThread
+ public static void setDurationScale(@FloatRange(from = 0) float durationScale) {
+ sDurationScale = durationScale;
+ List<WeakReference<DurationScaleChangeListener>> listenerCopy;
+
+ synchronized (sDurationScaleChangeListeners) {
+ listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
+ }
+
+ int listenersSize = listenerCopy.size();
+ for (int i = 0; i < listenersSize; i++) {
+ final DurationScaleChangeListener listener = listenerCopy.get(i).get();
+ if (listener != null) {
+ listener.onChanged(durationScale);
+ }
+ }
+ }
+
+ /**
+ * Returns the system-wide scaling factor for Animator-based animations.
+ *
+ * This affects both the start delay and duration of all such animations. Setting to 0 will
+ * cause animations to end immediately. The default value is 1.0f.
+ *
+ * @return the duration scale.
+ */
+ @FloatRange(from = 0)
+ public static float getDurationScale() {
+ return sDurationScale;
+ }
+
+ /**
+ * Registers a {@link DurationScaleChangeListener}
+ *
+ * This listens for changes to the system-wide scaling factor for Animator-based animations.
+ * Listeners will be called on the main thread.
+ *
+ * @param listener the listener to register.
+ * @return true if the listener was registered.
+ */
+ public static boolean registerDurationScaleChangeListener(
+ @NonNull DurationScaleChangeListener listener) {
+ int posToReplace = -1;
+ synchronized (sDurationScaleChangeListeners) {
+ for (int i = 0; i < sDurationScaleChangeListeners.size(); i++) {
+ final WeakReference<DurationScaleChangeListener> ref =
+ sDurationScaleChangeListeners.get(i);
+ if (ref.get() == null) {
+ if (posToReplace == -1) {
+ posToReplace = i;
+ }
+ } else if (ref.get() == listener) {
+ return false;
+ }
+ }
+ if (posToReplace != -1) {
+ sDurationScaleChangeListeners.set(posToReplace, new WeakReference<>(listener));
+ return true;
+ } else {
+ return sDurationScaleChangeListeners.add(new WeakReference<>(listener));
+ }
+ }
+ }
+
+ /**
+ * Unregisters a DurationScaleChangeListener.
+ *
+ * @see #registerDurationScaleChangeListener(DurationScaleChangeListener)
+ * @param listener the listener to unregister.
+ * @return true if the listener was unregistered.
+ */
+ public static boolean unregisterDurationScaleChangeListener(
+ @NonNull DurationScaleChangeListener listener) {
+ synchronized (sDurationScaleChangeListeners) {
+ WeakReference<DurationScaleChangeListener> listenerRefToRemove = null;
+ for (WeakReference<DurationScaleChangeListener> listenerRef :
+ sDurationScaleChangeListeners) {
+ if (listenerRef.get() == listener) {
+ listenerRefToRemove = listenerRef;
+ break;
+ }
+ }
+ return sDurationScaleChangeListeners.remove(listenerRefToRemove);
+ }
+ }
+
+ /**
+ * Returns whether animators are currently enabled, system-wide. By default, all
+ * animators are enabled. This can change if either the user sets a Developer Option
+ * to set the animator duration scale to 0 or by Battery Savery mode being enabled
+ * (which disables all animations).
+ *
+ * <p>Developers should not typically need to call this method, but should an app wish
+ * to show a different experience when animators are disabled, this return value
+ * can be used as a decider of which experience to offer.
+ *
+ * @return boolean Whether animators are currently enabled. The default value is
+ * <code>true</code>.
+ */
+ public static boolean areAnimatorsEnabled() {
+ return !(sDurationScale == 0);
+ }
+
+ /**
+ * Creates a new ValueAnimator object. This default constructor is primarily for
+ * use internally; the factory methods which take parameters are more generally
+ * useful.
+ */
+ public ValueAnimator() {
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between int values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofInt(int... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setIntValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofArgb(int... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setIntValues(values);
+ anim.setEvaluator(ArgbEvaluator.getInstance());
+ return anim;
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between float values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofFloat(float... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setFloatValues(values);
+ return anim;
+ }
+
+ /**
+ * Constructs and returns a ValueAnimator that animates between the values
+ * specified in the PropertyValuesHolder objects.
+ *
+ * @param values A set of PropertyValuesHolder objects whose values will be animated
+ * between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setValues(values);
+ return anim;
+ }
+ /**
+ * Constructs and returns a ValueAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this
+ * factory method also takes a TypeEvaluator object that the ValueAnimator will use
+ * to perform that interpolation.
+ *
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the ncessry interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setObjectValues(values);
+ anim.setEvaluator(evaluator);
+ return anim;
+ }
+
+ /**
+ * Sets int values that will be animated between. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+ * than one PropertyValuesHolder object, this method will set the values for the first
+ * of those objects.</p>
+ *
+ * @param values A set of values that the animation will animate between over time.
+ */
+ public void setIntValues(int... values) {
+ if (values == null || values.length == 0) {
+ return;
+ }
+ if (mValues == null || mValues.length == 0) {
+ setValues(PropertyValuesHolder.ofInt("", values));
+ } else {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ valuesHolder.setIntValues(values);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets float values that will be animated between. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+ * than one PropertyValuesHolder object, this method will set the values for the first
+ * of those objects.</p>
+ *
+ * @param values A set of values that the animation will animate between over time.
+ */
+ public void setFloatValues(float... values) {
+ if (values == null || values.length == 0) {
+ return;
+ }
+ if (mValues == null || mValues.length == 0) {
+ setValues(PropertyValuesHolder.ofFloat("", values));
+ } else {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ valuesHolder.setFloatValues(values);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets the values to animate between for this animation. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * <p><strong>Note:</strong> The Object values are stored as references to the original
+ * objects, which means that changes to those objects after this method is called will
+ * affect the values on the animator. If the objects will be mutated externally after
+ * this method is called, callers should pass a copy of those objects instead.
+ *
+ * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+ * than one PropertyValuesHolder object, this method will set the values for the first
+ * of those objects.</p>
+ *
+ * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate
+ * between these value objects. ValueAnimator only knows how to interpolate between the
+ * primitive types specified in the other setValues() methods.</p>
+ *
+ * @param values The set of values to animate between.
+ */
+ public void setObjectValues(Object... values) {
+ if (values == null || values.length == 0) {
+ return;
+ }
+ if (mValues == null || mValues.length == 0) {
+ setValues(PropertyValuesHolder.ofObject("", null, values));
+ } else {
+ PropertyValuesHolder valuesHolder = mValues[0];
+ valuesHolder.setObjectValues(values);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Sets the values, per property, being animated between. This function is called internally
+ * by the constructors of ValueAnimator that take a list of values. But a ValueAnimator can
+ * be constructed without values and this method can be called to set the values manually
+ * instead.
+ *
+ * @param values The set of values, per property, being animated between.
+ */
+ public void setValues(PropertyValuesHolder... values) {
+ int numValues = values.length;
+ mValues = values;
+ mValuesMap = new HashMap<>(numValues);
+ for (int i = 0; i < numValues; ++i) {
+ PropertyValuesHolder valuesHolder = values[i];
+ mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+ }
+ // New property/values/target should cause re-initialization prior to starting
+ mInitialized = false;
+ }
+
+ /**
+ * Returns the values that this ValueAnimator animates between. These values are stored in
+ * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list
+ * of value objects instead.
+ *
+ * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the
+ * values, per property, that define the animation.
+ */
+ public PropertyValuesHolder[] getValues() {
+ return mValues;
+ }
+
+ /**
+ * This function is called immediately before processing the first animation
+ * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+ * function is called after that delay ends.
+ * It takes care of the final initialization steps for the
+ * animation.
+ *
+ * <p>Overrides of this method should call the superclass method to ensure
+ * that internal mechanisms for the animation are set up correctly.</p>
+ */
+ @CallSuper
+ void initAnimation() {
+ if (!mInitialized) {
+ if (mValues != null) {
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].init();
+ }
+ }
+ mInitialized = true;
+ }
+ }
+
+ /**
+ * Sets the length of the animation. The default duration is 300 milliseconds.
+ *
+ * @param duration The length of the animation, in milliseconds. This value cannot
+ * be negative.
+ * @return ValueAnimator The object called with setDuration(). This return
+ * value makes it easier to compose statements together that construct and then set the
+ * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
+ */
+ @Override
+ public ValueAnimator setDuration(long duration) {
+ if (duration < 0) {
+ throw new IllegalArgumentException("Animators cannot have negative duration: " +
+ duration);
+ }
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * Overrides the global duration scale by a custom value.
+ *
+ * @param durationScale The duration scale to set; or {@code -1f} to use the global duration
+ * scale.
+ * @hide
+ */
+ public void overrideDurationScale(float durationScale) {
+ mDurationScale = durationScale;
+ }
+
+ private float resolveDurationScale() {
+ return mDurationScale >= 0f ? mDurationScale : sDurationScale;
+ }
+
+ private long getScaledDuration() {
+ return (long)(mDuration * resolveDurationScale());
+ }
+
+ /**
+ * Gets the length of the animation. The default duration is 300 milliseconds.
+ *
+ * @return The length of the animation, in milliseconds.
+ */
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public long getTotalDuration() {
+ if (mRepeatCount == INFINITE) {
+ return DURATION_INFINITE;
+ } else {
+ return mStartDelay + (mDuration * (mRepeatCount + 1));
+ }
+ }
+
+ /**
+ * Sets the position of the animation to the specified point in time. This time should
+ * be between 0 and the total duration of the animation, including any repetition. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this time; it will simply set the time to this value and perform any appropriate
+ * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+ * will set the current playing time to this value and continue playing from that point.
+ *
+ * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+ */
+ public void setCurrentPlayTime(long playTime) {
+ float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
+ setCurrentFraction(fraction);
+ }
+
+ /**
+ * Sets the position of the animation to the specified fraction. This fraction should
+ * be between 0 and the total fraction of the animation, including any repetition. That is,
+ * a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
+ * and a value of 2 at the end of a reversing animator that repeats once. If
+ * the animation has not yet been started, then it will not advance forward after it is
+ * set to this fraction; it will simply set the fraction to this value and perform any
+ * appropriate actions based on that fraction. If the animation is already running, then
+ * setCurrentFraction() will set the current fraction to this value and continue
+ * playing from that point. {@link Animator.AnimatorListener} events are not called
+ * due to changing the fraction; those events are only processed while the animation
+ * is running.
+ *
+ * @param fraction The fraction to which the animation is advanced or rewound. Values
+ * outside the range of 0 to the maximum fraction for the animator will be clamped to
+ * the correct range.
+ */
+ public void setCurrentFraction(float fraction) {
+ initAnimation();
+ fraction = clampFraction(fraction);
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
+ if (isPulsingInternal()) {
+ long seekTime = (long) (getScaledDuration() * fraction);
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ // Only modify the start time when the animation is running. Seek fraction will ensure
+ // non-running animations skip to the correct start time.
+ mStartTime = currentTime - seekTime;
+ } else {
+ // If the animation loop hasn't started, or during start delay, the startTime will be
+ // adjusted once the delay has passed based on seek fraction.
+ mSeekFraction = fraction;
+ }
+ mOverallFraction = fraction;
+ final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
+ animateValue(currentIterationFraction);
+ }
+
+ /**
+ * Calculates current iteration based on the overall fraction. The overall fraction will be
+ * in the range of [0, mRepeatCount + 1]. Both current iteration and fraction in the current
+ * iteration can be derived from it.
+ */
+ private int getCurrentIteration(float fraction) {
+ fraction = clampFraction(fraction);
+ // If the overall fraction is a positive integer, we consider the current iteration to be
+ // complete. In other words, the fraction for the current iteration would be 1, and the
+ // current iteration would be overall fraction - 1.
+ double iteration = Math.floor(fraction);
+ if (fraction == iteration && fraction > 0) {
+ iteration--;
+ }
+ return (int) iteration;
+ }
+
+ /**
+ * Calculates the fraction of the current iteration, taking into account whether the animation
+ * should be played backwards. E.g. When the animation is played backwards in an iteration,
+ * the fraction for that iteration will go from 1f to 0f.
+ */
+ private float getCurrentIterationFraction(float fraction, boolean inReverse) {
+ fraction = clampFraction(fraction);
+ int iteration = getCurrentIteration(fraction);
+ float currentFraction = fraction - iteration;
+ return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
+ }
+
+ /**
+ * Clamps fraction into the correct range: [0, mRepeatCount + 1]. If repeat count is infinite,
+ * no upper bound will be set for the fraction.
+ *
+ * @param fraction fraction to be clamped
+ * @return fraction clamped into the range of [0, mRepeatCount + 1]
+ */
+ private float clampFraction(float fraction) {
+ if (fraction < 0) {
+ fraction = 0;
+ } else if (mRepeatCount != INFINITE) {
+ fraction = Math.min(fraction, mRepeatCount + 1);
+ }
+ return fraction;
+ }
+
+ /**
+ * Calculates the direction of animation playing (i.e. forward or backward), based on 1)
+ * whether the entire animation is being reversed, 2) repeat mode applied to the current
+ * iteration.
+ */
+ private boolean shouldPlayBackward(int iteration, boolean inReverse) {
+ if (iteration > 0 && mRepeatMode == REVERSE &&
+ (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
+ // if we were seeked to some other iteration in a reversing animator,
+ // figure out the correct direction to start playing based on the iteration
+ if (inReverse) {
+ return (iteration % 2) == 0;
+ } else {
+ return (iteration % 2) != 0;
+ }
+ } else {
+ return inReverse;
+ }
+ }
+
+ /**
+ * Gets the current position of the animation in time, which is equal to the current
+ * time minus the time that the animation started. An animation that is not yet started will
+ * return a value of zero, unless the animation has has its play time set via
+ * {@link #setCurrentPlayTime(long)} or {@link #setCurrentFraction(float)}, in which case
+ * it will return the time that was set.
+ *
+ * @return The current position in time of the animation.
+ */
+ public long getCurrentPlayTime() {
+ if (!mInitialized || (!mStarted && mSeekFraction < 0)) {
+ return 0;
+ }
+ if (mSeekFraction >= 0) {
+ return (long) (mDuration * mSeekFraction);
+ }
+ float durationScale = resolveDurationScale();
+ if (durationScale == 0f) {
+ durationScale = 1f;
+ }
+ return (long) ((AnimationUtils.currentAnimationTimeMillis() - mStartTime) / durationScale);
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called.
+ *
+ * @return the number of milliseconds to delay running the animation
+ */
+ @Override
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation after
+ * {@link #start()} is called. Note that the start delay should always be non-negative. Any
+ * negative start delay will be clamped to 0 on N and above.
+ *
+ * @param startDelay The amount of the delay, in milliseconds
+ */
+ @Override
+ public void setStartDelay(long startDelay) {
+ // Clamp start delay to non-negative range.
+ if (startDelay < 0) {
+ Log.w(TAG, "Start delay should always be non-negative");
+ startDelay = 0;
+ }
+ mStartDelay = startDelay;
+ }
+
+ /**
+ * The amount of time, in milliseconds, between each frame of the animation. This is a
+ * requested time that the animation will attempt to honor, but the actual delay between
+ * frames may be different, depending on system load and capabilities. This is a static
+ * function because the same delay will be applied to all animations, since they are all
+ * run off of a single timing loop.
+ *
+ * The frame delay may be ignored when the animation system uses an external timing
+ * source, such as the display refresh rate (vsync), to govern animations.
+ *
+ * Note that this method should be called from the same thread that {@link #start()} is
+ * called in order to check the frame delay for that animation. A runtime exception will be
+ * thrown if the calling thread does not have a Looper.
+ *
+ * @return the requested time between frames, in milliseconds
+ */
+ public static long getFrameDelay() {
+ return AnimationHandler.getInstance().getFrameDelay();
+ }
+
+ /**
+ * The amount of time, in milliseconds, between each frame of the animation. This is a
+ * requested time that the animation will attempt to honor, but the actual delay between
+ * frames may be different, depending on system load and capabilities. This is a static
+ * function because the same delay will be applied to all animations, since they are all
+ * run off of a single timing loop.
+ *
+ * The frame delay may be ignored when the animation system uses an external timing
+ * source, such as the display refresh rate (vsync), to govern animations.
+ *
+ * Note that this method should be called from the same thread that {@link #start()} is
+ * called in order to have the new frame delay take effect on that animation. A runtime
+ * exception will be thrown if the calling thread does not have a Looper.
+ *
+ * @param frameDelay the requested time between frames, in milliseconds
+ */
+ public static void setFrameDelay(long frameDelay) {
+ AnimationHandler.getInstance().setFrameDelay(frameDelay);
+ }
+
+ /**
+ * The most recent value calculated by this <code>ValueAnimator</code> when there is just one
+ * property being animated. This value is only sensible while the animation is running. The main
+ * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code>
+ * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+ * is called during each animation frame, immediately after the value is calculated.
+ *
+ * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for
+ * the single property being animated. If there are several properties being animated
+ * (specified by several PropertyValuesHolder objects in the constructor), this function
+ * returns the animated value for the first of those objects.
+ */
+ public Object getAnimatedValue() {
+ if (mValues != null && mValues.length > 0) {
+ return mValues[0].getAnimatedValue();
+ }
+ // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
+ return null;
+ }
+
+ /**
+ * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>.
+ * The main purpose for this read-only property is to retrieve the value from the
+ * <code>ValueAnimator</code> during a call to
+ * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+ * is called during each animation frame, immediately after the value is calculated.
+ *
+ * @return animatedValue The value most recently calculated for the named property
+ * by this <code>ValueAnimator</code>.
+ */
+ public Object getAnimatedValue(String propertyName) {
+ PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
+ if (valuesHolder != null) {
+ return valuesHolder.getAnimatedValue();
+ } else {
+ // At least avoid crashing if called with bogus propertyName
+ return null;
+ }
+ }
+
+ /**
+ * Sets how many times the animation should be repeated. If the repeat
+ * count is 0, the animation is never repeated. If the repeat count is
+ * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+ * into account. The repeat count is 0 by default.
+ *
+ * @param value the number of times the animation should be repeated
+ */
+ public void setRepeatCount(int value) {
+ mRepeatCount = value;
+ }
+ /**
+ * Defines how many times the animation should repeat. The default value
+ * is 0.
+ *
+ * @return the number of times the animation should repeat, or {@link #INFINITE}
+ */
+ public int getRepeatCount() {
+ return mRepeatCount;
+ }
+
+ /**
+ * Defines what this animation should do when it reaches the end. This
+ * setting is applied only when the repeat count is either greater than
+ * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+ *
+ * @param value {@link #RESTART} or {@link #REVERSE}
+ */
+ public void setRepeatMode(@RepeatMode int value) {
+ mRepeatMode = value;
+ }
+
+ /**
+ * Defines what this animation should do when it reaches the end.
+ *
+ * @return either one of {@link #REVERSE} or {@link #RESTART}
+ */
+ @RepeatMode
+ public int getRepeatMode() {
+ return mRepeatMode;
+ }
+
+ /**
+ * Adds a listener to the set of listeners that are sent update events through the life of
+ * an animation. This method is called on all listeners for every frame of the animation,
+ * after the values for the animation have been calculated.
+ *
+ * @param listener the listener to be added to the current set of listeners for this animation.
+ */
+ public void addUpdateListener(AnimatorUpdateListener listener) {
+ if (mUpdateListeners == null) {
+ mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
+ }
+ mUpdateListeners.add(listener);
+ }
+
+ /**
+ * Removes all listeners from the set listening to frame updates for this animation.
+ */
+ public void removeAllUpdateListeners() {
+ if (mUpdateListeners == null) {
+ return;
+ }
+ mUpdateListeners.clear();
+ mUpdateListeners = null;
+ }
+
+ /**
+ * Removes a listener from the set listening to frame updates for this animation.
+ *
+ * @param listener the listener to be removed from the current set of update listeners
+ * for this animation.
+ */
+ public void removeUpdateListener(AnimatorUpdateListener listener) {
+ if (mUpdateListeners == null) {
+ return;
+ }
+ mUpdateListeners.remove(listener);
+ if (mUpdateListeners.size() == 0) {
+ mUpdateListeners = null;
+ }
+ }
+
+
+ /**
+ * The time interpolator used in calculating the elapsed fraction of this animation. The
+ * interpolator determines whether the animation runs with linear or non-linear motion,
+ * such as acceleration and deceleration. The default value is
+ * {@link android.view.animation.AccelerateDecelerateInterpolator}
+ *
+ * @param value the interpolator to be used by this animation. A value of <code>null</code>
+ * will result in linear interpolation.
+ */
+ @Override
+ public void setInterpolator(TimeInterpolator value) {
+ if (value != null) {
+ mInterpolator = value;
+ } else {
+ mInterpolator = new LinearInterpolator();
+ }
+ }
+
+ /**
+ * Returns the timing interpolator that this ValueAnimator uses.
+ *
+ * @return The timing interpolator for this ValueAnimator.
+ */
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * The type evaluator to be used when calculating the animated values of this animation.
+ * The system will automatically assign a float or int evaluator based on the type
+ * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values
+ * are not one of these primitive types, or if different evaluation is desired (such as is
+ * necessary with int values that represent colors), a custom evaluator needs to be assigned.
+ * For example, when running an animation on color values, the {@link ArgbEvaluator}
+ * should be used to get correct RGB color interpolation.
+ *
+ * <p>If this ValueAnimator has only one set of values being animated between, this evaluator
+ * will be used for that set. If there are several sets of values being animated, which is
+ * the case if PropertyValuesHolder objects were set on the ValueAnimator, then the evaluator
+ * is assigned just to the first PropertyValuesHolder object.</p>
+ *
+ * @param value the evaluator to be used this animation
+ */
+ public void setEvaluator(TypeEvaluator value) {
+ if (value != null && mValues != null && mValues.length > 0) {
+ mValues[0].setEvaluator(value);
+ }
+ }
+
+ /**
+ * Start the animation playing. This version of start() takes a boolean flag that indicates
+ * whether the animation should play in reverse. The flag is usually false, but may be set
+ * to true if called from the reverse() method.
+ *
+ * <p>The animation started by calling this method will be run on the thread that called
+ * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+ * this is not the case). Also, if the animation will animate
+ * properties of objects in the view hierarchy, then the calling thread should be the UI
+ * thread for that view hierarchy.</p>
+ *
+ * @param playBackwards Whether the ValueAnimator should start playing in reverse.
+ */
+ private void start(boolean playBackwards) {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ mReversing = playBackwards;
+ mSelfPulse = !mSuppressSelfPulseRequested;
+ // Special case: reversing from seek-to-0 should act as if not seeked at all.
+ if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
+ if (mRepeatCount == INFINITE) {
+ // Calculate the fraction of the current iteration.
+ float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
+ mSeekFraction = 1 - fraction;
+ } else {
+ mSeekFraction = 1 + mRepeatCount - mSeekFraction;
+ }
+ }
+ mStarted = true;
+ mPaused = false;
+ mRunning = false;
+ mAnimationEndRequested = false;
+ // Resets mLastFrameTime when start() is called, so that if the animation was running,
+ // calling start() would put the animation in the
+ // started-but-not-yet-reached-the-first-frame phase.
+ mLastFrameTime = -1;
+ mFirstFrameTime = -1;
+ mStartTime = -1;
+ addAnimationCallback(0);
+
+ if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
+ // If there's no start delay, init the animation and notify start listeners right away
+ // to be consistent with the previous behavior. Otherwise, postpone this until the first
+ // frame after the start delay.
+ startAnimation();
+ if (mSeekFraction == -1) {
+ // No seek, start at play time 0. Note that the reason we are not using fraction 0
+ // is because for animations with 0 duration, we want to be consistent with pre-N
+ // behavior: skip to the final value immediately.
+ setCurrentPlayTime(0);
+ } else {
+ setCurrentFraction(mSeekFraction);
+ }
+ }
+ }
+
+ void startWithoutPulsing(boolean inReverse) {
+ mSuppressSelfPulseRequested = true;
+ if (inReverse) {
+ reverse();
+ } else {
+ start();
+ }
+ mSuppressSelfPulseRequested = false;
+ }
+
+ @Override
+ public void start() {
+ start(false);
+ }
+
+ @Override
+ public void cancel() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+
+ // If end has already been requested, through a previous end() or cancel() call, no-op
+ // until animation starts again.
+ if (mAnimationEndRequested) {
+ return;
+ }
+
+ // Only cancel if the animation is actually running or has been started and is about
+ // to run
+ // Only notify listeners if the animator has actually started
+ if ((mStarted || mRunning || mStartListenersCalled) && mListeners != null) {
+ if (!mRunning) {
+ // If it's not yet running, then start listeners weren't called. Call them now.
+ notifyStartListeners(mReversing);
+ }
+ notifyListeners(AnimatorCaller.ON_CANCEL, false);
+ }
+ endAnimation();
+ }
+
+ @Override
+ public void end() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+ }
+ if (!mRunning) {
+ // Special case if the animation has not yet started; get it ready for ending
+ startAnimation();
+ mStarted = true;
+ } else if (!mInitialized) {
+ initAnimation();
+ }
+ animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f);
+ endAnimation();
+ }
+
+ @Override
+ public void resume() {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException("Animators may only be resumed from the same " +
+ "thread that the animator was started on");
+ }
+ if (mPaused && !mResumed) {
+ mResumed = true;
+ if (mPauseTime > 0) {
+ addAnimationCallback(0);
+ }
+ }
+ super.resume();
+ }
+
+ @Override
+ public void pause() {
+ boolean previouslyPaused = mPaused;
+ super.pause();
+ if (!previouslyPaused && mPaused) {
+ mPauseTime = -1;
+ mResumed = false;
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ /**
+ * Plays the ValueAnimator in reverse. If the animation is already running,
+ * it will stop itself and play backwards from the point reached when reverse was called.
+ * If the animation is not currently running, then it will start from the end and
+ * play backwards. This behavior is only set for the current animation; future playing
+ * of the animation will use the default behavior of playing forward.
+ */
+ @Override
+ public void reverse() {
+ if (isPulsingInternal()) {
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ long currentPlayTime = currentTime - mStartTime;
+ long timeLeft = getScaledDuration() - currentPlayTime;
+ mStartTime = currentTime - timeLeft;
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
+ mReversing = !mReversing;
+ } else if (mStarted) {
+ mReversing = !mReversing;
+ end();
+ } else {
+ start(true);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean canReverse() {
+ return true;
+ }
+
+ /**
+ * Called internally to end an animation by removing it from the animations list. Must be
+ * called on the UI thread.
+ */
+ private void endAnimation() {
+ if (mAnimationEndRequested) {
+ return;
+ }
+ removeAnimationCallback();
+
+ mAnimationEndRequested = true;
+ mPaused = false;
+ boolean notify = (mStarted || mRunning) && mListeners != null;
+ if (notify && !mRunning) {
+ // If it's not yet running, then start listeners weren't called. Call them now.
+ notifyStartListeners(mReversing);
+ }
+ mLastFrameTime = -1;
+ mFirstFrameTime = -1;
+ mStartTime = -1;
+ mRunning = false;
+ mStarted = false;
+ notifyEndListeners(mReversing);
+ // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
+ mReversing = false;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
+ System.identityHashCode(this));
+ }
+ }
+
+ /**
+ * Called internally to start an animation by adding it to the active animations list. Must be
+ * called on the UI thread.
+ */
+ private void startAnimation() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
+ System.identityHashCode(this));
+ }
+
+ mAnimationEndRequested = false;
+ initAnimation();
+ mRunning = true;
+ if (mSeekFraction >= 0) {
+ mOverallFraction = mSeekFraction;
+ } else {
+ mOverallFraction = 0f;
+ }
+
+ notifyStartListeners(mReversing);
+ }
+
+ /**
+ * Internal only: This tracks whether the animation has gotten on the animation loop. Note
+ * this is different than {@link #isRunning()} in that the latter tracks the time after start()
+ * is called (or after start delay if any), which may be before the animation loop starts.
+ */
+ private boolean isPulsingInternal() {
+ return mLastFrameTime >= 0;
+ }
+
+ /**
+ * Returns the name of this animator for debugging purposes.
+ */
+ String getNameForTrace() {
+ return "animator";
+ }
+
+ /**
+ * Applies an adjustment to the animation to compensate for jank between when
+ * the animation first ran and when the frame was drawn.
+ * @hide
+ */
+ public void commitAnimationFrame(long frameTime) {
+ if (!mStartTimeCommitted) {
+ mStartTimeCommitted = true;
+ long adjustment = frameTime - mLastFrameTime;
+ if (adjustment > 0) {
+ mStartTime += adjustment;
+ if (DEBUG) {
+ Log.d(TAG, "Adjusted start time by " + adjustment + " ms: " + toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * This internal function processes a single animation frame for a given animation. The
+ * currentTime parameter is the timing pulse sent by the handler, used to calculate the
+ * elapsed duration, and therefore
+ * the elapsed fraction, of the animation. The return value indicates whether the animation
+ * should be ended (which happens when the elapsed time of the animation exceeds the
+ * animation's duration, including the repeatCount).
+ *
+ * @param currentTime The current time, as tracked by the static timing handler
+ * @return true if the animation's duration, including any repetitions due to
+ * <code>repeatCount</code> has been exceeded and the animation should be ended.
+ */
+ boolean animateBasedOnTime(long currentTime) {
+ boolean done = false;
+ if (mRunning) {
+ final long scaledDuration = getScaledDuration();
+ final float fraction = scaledDuration > 0 ?
+ (float)(currentTime - mStartTime) / scaledDuration : 1f;
+ final float lastFraction = mOverallFraction;
+ final boolean newIteration = (int) fraction > (int) lastFraction;
+ final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
+ (mRepeatCount != INFINITE);
+ if (scaledDuration == 0) {
+ // 0 duration animator, ignore the repeat count and skip to the end
+ done = true;
+ } else if (newIteration && !lastIterationFinished) {
+ // Time to repeat
+ notifyListeners(AnimatorCaller.ON_REPEAT, false);
+ } else if (lastIterationFinished) {
+ done = true;
+ }
+ mOverallFraction = clampFraction(fraction);
+ float currentIterationFraction = getCurrentIterationFraction(
+ mOverallFraction, mReversing);
+ animateValue(currentIterationFraction);
+ }
+ return done;
+ }
+
+ /**
+ * Internal use only.
+ *
+ * This method does not modify any fields of the animation. It should be called when seeking
+ * in an AnimatorSet. When the last play time and current play time are of different repeat
+ * iterations,
+ * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)}
+ * will be called.
+ */
+ @Override
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
+ throw new UnsupportedOperationException("Error: Play time should never be negative.");
+ }
+
+ initAnimation();
+ long duration = getTotalDuration();
+ if (notify) {
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else if (lastPlayTime > duration
+ || (lastPlayTime == duration && currentPlayTime < duration)
+ ) {
+ notifyStartListeners(true);
+ }
+ }
+ if (duration >= 0) {
+ lastPlayTime = Math.min(duration, lastPlayTime);
+ }
+ lastPlayTime -= mStartDelay;
+ currentPlayTime -= mStartDelay;
+
+ // Check whether repeat callback is needed only when repeat count is non-zero
+ if (mRepeatCount > 0) {
+ int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
+ int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
+
+ // Clamp iteration to [0, mRepeatCount]
+ iteration = Math.min(iteration, mRepeatCount);
+ lastIteration = Math.min(lastIteration, mRepeatCount);
+
+ if (notify && iteration != lastIteration) {
+ notifyListeners(AnimatorCaller.ON_REPEAT, false);
+ }
+ }
+
+ if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
+ throw new IllegalStateException("Can't animate a value outside of the duration");
+ } else {
+ // Find the current fraction:
+ float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
+ fraction = getCurrentIterationFraction(fraction, false);
+ animateValue(fraction);
+ }
+ }
+
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ boolean inReverse = currentPlayTime < lastPlayTime;
+ boolean doSkip;
+ if (currentPlayTime <= 0 && lastPlayTime > 0) {
+ doSkip = true;
+ } else {
+ long duration = getTotalDuration();
+ doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
+ }
+ if (doSkip) {
+ if (notify) {
+ notifyStartListeners(inReverse);
+ }
+ skipToEndValue(inReverse);
+ if (notify) {
+ notifyEndListeners(inReverse);
+ }
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Skips the animation value to end/start, depending on whether the play direction is forward
+ * or backward.
+ *
+ * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+ * equivalent to skip to start value in a forward playing direction.
+ */
+ void skipToEndValue(boolean inReverse) {
+ initAnimation();
+ float endFraction = inReverse ? 0f : 1f;
+ if (mRepeatCount % 2 == 1 && mRepeatMode == REVERSE) {
+ // This would end on fraction = 0
+ endFraction = 0f;
+ }
+ animateValue(endFraction);
+ }
+
+ @Override
+ boolean isInitialized() {
+ return mInitialized;
+ }
+
+ /**
+ * Processes a frame of the animation, adjusting the start time if needed.
+ *
+ * @param frameTime The frame time.
+ * @return true if the animation has ended.
+ * @hide
+ */
+ public final boolean doAnimationFrame(long frameTime) {
+ if (mStartTime < 0) {
+ // First frame. If there is start delay, start delay count down will happen *after* this
+ // frame.
+ mStartTime = mReversing
+ ? frameTime
+ : frameTime + (long) (mStartDelay * resolveDurationScale());
+ }
+
+ // Handle pause/resume
+ if (mPaused) {
+ mPauseTime = frameTime;
+ removeAnimationCallback();
+ return false;
+ } else if (mResumed) {
+ mResumed = false;
+ if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mStartTime += (frameTime - mPauseTime);
+ }
+ }
+
+ if (!mRunning) {
+ // If not running, that means the animation is in the start delay phase of a forward
+ // running animation. In the case of reversing, we want to run start delay in the end.
+ if (mStartTime > frameTime && mSeekFraction == -1) {
+ // This is when no seek fraction is set during start delay. If developers change the
+ // seek fraction during the delay, animation will start from the seeked position
+ // right away.
+ return false;
+ } else {
+ // If mRunning is not set by now, that means non-zero start delay,
+ // no seeking, not reversing. At this point, start delay has passed.
+ mRunning = true;
+ startAnimation();
+ }
+ }
+
+ if (mLastFrameTime < 0) {
+ if (mSeekFraction >= 0) {
+ long seekTime = (long) (getScaledDuration() * mSeekFraction);
+ mStartTime = frameTime - seekTime;
+ mSeekFraction = -1;
+ }
+ mStartTimeCommitted = false; // allow start time to be compensated for jank
+ }
+ mLastFrameTime = frameTime;
+ // The frame time might be before the start time during the first frame of
+ // an animation. The "current time" must always be on or after the start
+ // time to avoid animating frames at negative time intervals. In practice, this
+ // is very rare and only happens when seeking backwards.
+ final long currentTime = Math.max(frameTime, mStartTime);
+ boolean finished = animateBasedOnTime(currentTime);
+
+ if (finished) {
+ endAnimation();
+ }
+ return finished;
+ }
+
+ @Override
+ boolean pulseAnimationFrame(long frameTime) {
+ if (mSelfPulse) {
+ // Pulse animation frame will *always* be after calling start(). If mSelfPulse isn't
+ // set to false at this point, that means child animators did not call super's start().
+ // This can happen when the Animator is just a non-animating wrapper around a real
+ // functional animation. In this case, we can't really pulse a frame into the animation,
+ // because the animation cannot necessarily be properly initialized (i.e. no start/end
+ // values set).
+ return false;
+ }
+ return doAnimationFrame(frameTime);
+ }
+
+ private void addOneShotCommitCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ getAnimationHandler().addOneShotCommitCallback(this);
+ }
+
+ private void removeAnimationCallback() {
+ if (!mSelfPulse) {
+ return;
+ }
+ getAnimationHandler().removeCallback(this);
+ }
+
+ private void addAnimationCallback(long delay) {
+ if (!mSelfPulse) {
+ return;
+ }
+ getAnimationHandler().addAnimationFrameCallback(this, delay);
+ }
+
+ /**
+ * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
+ * the most recent frame update on the animation.
+ *
+ * @return Elapsed/interpolated fraction of the animation.
+ */
+ public float getAnimatedFraction() {
+ return mCurrentFraction;
+ }
+
+ /**
+ * This method is called with the elapsed fraction of the animation during every
+ * animation frame. This function turns the elapsed fraction into an interpolated fraction
+ * and then into an animated value (from the evaluator. The function is called mostly during
+ * animation updates, but it is also called when the <code>end()</code>
+ * function is called, to set the final value on the property.
+ *
+ * <p>Overrides of this method must call the superclass to perform the calculation
+ * of the animated value.</p>
+ *
+ * @param fraction The elapsed fraction of the animation.
+ */
+ @CallSuper
+ @UnsupportedAppUsage
+ void animateValue(float fraction) {
+ if (TRACE_ANIMATION_FRACTION) {
+ Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
+ (int) (fraction * 1000));
+ }
+ if (mValues == null) {
+ return;
+ }
+ fraction = mInterpolator.getInterpolation(fraction);
+ mCurrentFraction = fraction;
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].calculateValue(fraction);
+ }
+ if (mSeekFraction >= 0 || mStartListenersCalled) {
+ callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
+ }
+ }
+
+ @Override
+ public ValueAnimator clone() {
+ final ValueAnimator anim = (ValueAnimator) super.clone();
+ if (mUpdateListeners != null) {
+ anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners);
+ }
+ anim.mSeekFraction = -1;
+ anim.mReversing = false;
+ anim.mInitialized = false;
+ anim.mStarted = false;
+ anim.mRunning = false;
+ anim.mPaused = false;
+ anim.mResumed = false;
+ anim.mStartTime = -1;
+ anim.mStartTimeCommitted = false;
+ anim.mAnimationEndRequested = false;
+ anim.mPauseTime = -1;
+ anim.mLastFrameTime = -1;
+ anim.mFirstFrameTime = -1;
+ anim.mOverallFraction = 0;
+ anim.mCurrentFraction = 0;
+ anim.mSelfPulse = true;
+ anim.mSuppressSelfPulseRequested = false;
+
+ PropertyValuesHolder[] oldValues = mValues;
+ if (oldValues != null) {
+ int numValues = oldValues.length;
+ anim.mValues = new PropertyValuesHolder[numValues];
+ anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+ for (int i = 0; i < numValues; ++i) {
+ PropertyValuesHolder newValuesHolder = oldValues[i].clone();
+ anim.mValues[i] = newValuesHolder;
+ anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
+ }
+ }
+ return anim;
+ }
+
+ /**
+ * Implementors of this interface can add themselves as update listeners
+ * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
+ * frame, after the current frame's values have been calculated for that
+ * <code>ValueAnimator</code>.
+ */
+ public static interface AnimatorUpdateListener {
+ /**
+ * <p>Notifies the occurrence of another frame of the animation.</p>
+ *
+ * @param animation The animation which was repeated.
+ */
+ void onAnimationUpdate(@NonNull ValueAnimator animation);
+
+ }
+
+ /**
+ * Return the number of animations currently running.
+ *
+ * Used by StrictMode internally to annotate violations.
+ * May be called on arbitrary threads!
+ *
+ * @hide
+ */
+ public static int getCurrentAnimationsCount() {
+ return AnimationHandler.getAnimationCount();
+ }
+
+ @Override
+ public String toString() {
+ String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode());
+ if (mValues != null) {
+ for (int i = 0; i < mValues.length; ++i) {
+ returnVal += "\n " + mValues[i].toString();
+ }
+ }
+ return returnVal;
+ }
+
+ /**
+ * <p>Whether or not the ValueAnimator is allowed to run asynchronously off of
+ * the UI thread. This is a hint that informs the ValueAnimator that it is
+ * OK to run the animation off-thread, however ValueAnimator may decide
+ * that it must run the animation on the UI thread anyway. For example if there
+ * is an {@link AnimatorUpdateListener} the animation will run on the UI thread,
+ * regardless of the value of this hint.</p>
+ *
+ * <p>Regardless of whether or not the animation runs asynchronously, all
+ * listener callbacks will be called on the UI thread.</p>
+ *
+ * <p>To be able to use this hint the following must be true:</p>
+ * <ol>
+ * <li>{@link #getAnimatedFraction()} is not needed (it will return undefined values).</li>
+ * <li>The animator is immutable while {@link #isStarted()} is true. Requests
+ * to change values, duration, delay, etc... may be ignored.</li>
+ * <li>Lifecycle callback events may be asynchronous. Events such as
+ * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
+ * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
+ * as they must be posted back to the UI thread, and any actions performed
+ * by those callbacks (such as starting new animations) will not happen
+ * in the same frame.</li>
+ * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
+ * may be asynchronous. It is guaranteed that all state changes that are
+ * performed on the UI thread in the same frame will be applied as a single
+ * atomic update, however that frame may be the current frame,
+ * the next frame, or some future frame. This will also impact the observed
+ * state of the Animator. For example, {@link #isStarted()} may still return true
+ * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
+ * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
+ * for this reason.</li>
+ * </ol>
+ * @hide
+ */
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ // It is up to subclasses to support this, if they can.
+ }
+
+ /**
+ * @return The {@link AnimationHandler} that will be used to schedule updates for this animator.
+ * @hide
+ */
+ public AnimationHandler getAnimationHandler() {
+ return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
+ }
+
+ /**
+ * Sets the animation handler used to schedule updates for this animator or {@code null} to use
+ * the default handler.
+ * @hide
+ */
+ public void setAnimationHandler(@Nullable AnimationHandler animationHandler) {
+ mAnimationHandler = animationHandler;
+ }
+
+ /**
+ * Listener interface for the system-wide scaling factor for Animator-based animations.
+ *
+ * @see #registerDurationScaleChangeListener(DurationScaleChangeListener)
+ * @see #unregisterDurationScaleChangeListener(DurationScaleChangeListener)
+ */
+ public interface DurationScaleChangeListener {
+ /**
+ * Called when the duration scale changes.
+ * @param scale the duration scale
+ */
+ void onChanged(@FloatRange(from = 0) float scale);
+ }
+}
diff --git a/android-34/android/annotation/AnimRes.java b/android-34/android/annotation/AnimRes.java
new file mode 100644
index 0000000..56f8acf
--- /dev/null
+++ b/android-34/android/annotation/AnimRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnimRes {
+}
diff --git a/android-34/android/annotation/AnimatorRes.java b/android-34/android/annotation/AnimatorRes.java
new file mode 100644
index 0000000..cd4c189
--- /dev/null
+++ b/android-34/android/annotation/AnimatorRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnimatorRes {
+}
diff --git a/android-34/android/annotation/AnyRes.java b/android-34/android/annotation/AnyRes.java
new file mode 100644
index 0000000..44411a0
--- /dev/null
+++ b/android-34/android/annotation/AnyRes.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a resource reference of any type. If the specific type is known, use
+ * one of the more specific annotations instead, such as {@link StringRes} or
+ * {@link DrawableRes}.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnyRes {
+}
diff --git a/android-34/android/annotation/AnyThread.java b/android-34/android/annotation/AnyThread.java
new file mode 100644
index 0000000..ee36a42
--- /dev/null
+++ b/android-34/android/annotation/AnyThread.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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method can be called from any thread (e.g. it is
+ * "thread safe".) If the annotated element is a class, then all methods in the
+ * class can be called from any thread.
+ * <p>
+ * The main purpose of this method is to indicate that you believe a method can
+ * be called from any thread; static tools can then check that nothing you call
+ * from within this method or class have more strict threading requirements.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @AnyThread
+ * public void deliverResult(D data) { ... }
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method is safe to call from any thread.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface AnyThread {
+}
diff --git a/android-34/android/annotation/AppIdInt.java b/android-34/android/annotation/AppIdInt.java
new file mode 100644
index 0000000..29838dd
--- /dev/null
+++ b/android-34/android/annotation/AppIdInt.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element is a multi-user application ID. This is
+ * <em>not</em> the same as a UID.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AppIdInt {
+}
diff --git a/android-34/android/annotation/ArrayRes.java b/android-34/android/annotation/ArrayRes.java
new file mode 100644
index 0000000..1407af1
--- /dev/null
+++ b/android-34/android/annotation/ArrayRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ArrayRes {
+}
diff --git a/android-34/android/annotation/AttrRes.java b/android-34/android/annotation/AttrRes.java
new file mode 100644
index 0000000..285b80c
--- /dev/null
+++ b/android-34/android/annotation/AttrRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an attribute reference (e.g. {@link android.R.attr#action}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AttrRes {
+}
diff --git a/android-34/android/annotation/BinderThread.java b/android-34/android/annotation/BinderThread.java
new file mode 100644
index 0000000..ca5e14c
--- /dev/null
+++ b/android-34/android/annotation/BinderThread.java
@@ -0,0 +1,43 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated method should only be called on the binder thread.
+ * If the annotated element is a class, then all methods in the class should be called
+ * on the binder thread.
+ * <p>
+ * Example:
+ * <pre><code>
+ * @BinderThread
+ * public BeamShareData createBeamShareData() { ... }
+ * </code></pre>
+ *
+ * {@hide}
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface BinderThread {
+}
\ No newline at end of file
diff --git a/android-34/android/annotation/BoolRes.java b/android-34/android/annotation/BoolRes.java
new file mode 100644
index 0000000..f50785b
--- /dev/null
+++ b/android-34/android/annotation/BoolRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a boolean resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface BoolRes {
+}
diff --git a/android-34/android/annotation/BroadcastBehavior.java b/android-34/android/annotation/BroadcastBehavior.java
new file mode 100644
index 0000000..87bf554
--- /dev/null
+++ b/android-34/android/annotation/BroadcastBehavior.java
@@ -0,0 +1,59 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Description of how the annotated broadcast action behaves.
+ *
+ * @hide
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.SOURCE)
+public @interface BroadcastBehavior {
+ /**
+ * This broadcast will only be delivered to an explicit target.
+ *
+ * @see android.content.Intent#setPackage(String)
+ * @see android.content.Intent#setComponent(android.content.ComponentName)
+ */
+ boolean explicitOnly() default false;
+
+ /**
+ * This broadcast will only be delivered to registered receivers.
+ *
+ * @see android.content.Intent#FLAG_RECEIVER_REGISTERED_ONLY
+ */
+ boolean registeredOnly() default false;
+
+ /**
+ * This broadcast will include all {@code AndroidManifest.xml} receivers
+ * regardless of process state.
+ *
+ * @see android.content.Intent#FLAG_RECEIVER_INCLUDE_BACKGROUND
+ */
+ boolean includeBackground() default false;
+
+ /**
+ * This broadcast is protected and can only be sent by the OS.
+ */
+ boolean protectedBroadcast() default false;
+}
diff --git a/android-34/android/annotation/BytesLong.java b/android-34/android/annotation/BytesLong.java
new file mode 100644
index 0000000..f5e1a9c
--- /dev/null
+++ b/android-34/android/annotation/BytesLong.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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative number of bytes.
+ * @paramDoc Value is a non-negative number of bytes.
+ * @returnDoc Value is a non-negative number of bytes.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface BytesLong {
+}
diff --git a/android-34/android/annotation/CallSuper.java b/android-34/android/annotation/CallSuper.java
new file mode 100644
index 0000000..c16b511
--- /dev/null
+++ b/android-34/android/annotation/CallSuper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that any overriding methods should invoke this method as well.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @CallSuper
+ * public abstract void onFocusLost();
+ * </code>
+ * </pre>
+ *
+ * @memberDoc If you override this method you <em>must</em> call through to the
+ * superclass implementation.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface CallSuper {
+}
diff --git a/android-34/android/annotation/CallbackExecutor.java b/android-34/android/annotation/CallbackExecutor.java
new file mode 100644
index 0000000..9529d3e
--- /dev/null
+++ b/android-34/android/annotation/CallbackExecutor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.concurrent.Executor;
+
+/**
+ * @paramDoc Callback and listener events are dispatched through this
+ * {@link Executor}, providing an easy way to control which thread is
+ * used. To dispatch events through the main thread of your
+ * application, you can use
+ * {@link android.content.Context#getMainExecutor() Context.getMainExecutor()}.
+ * Otherwise, provide an {@link Executor} that dispatches to an appropriate thread.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(PARAMETER)
+public @interface CallbackExecutor {
+}
diff --git a/android-34/android/annotation/CheckResult.java b/android-34/android/annotation/CheckResult.java
new file mode 100644
index 0000000..97d031a
--- /dev/null
+++ b/android-34/android/annotation/CheckResult.java
@@ -0,0 +1,58 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated method returns a result that it typically is
+ * an error to ignore. This is usually used for methods that have no side effect,
+ * so calling it without actually looking at the result usually means the developer
+ * has misunderstood what the method does.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public @CheckResult String trim(String s) { return s.trim(); }
+ * ...
+ * s.trim(); // this is probably an error
+ * s = s.trim(); // ok
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface CheckResult {
+ /** Defines the name of the suggested method to use instead, if applicable (using
+ * the same signature format as javadoc.) If there is more than one possibility,
+ * list them all separated by commas.
+ * <p>
+ * For example, ProcessBuilder has a method named {@code redirectErrorStream()}
+ * which sounds like it might redirect the error stream. It does not. It's just
+ * a getter which returns whether the process builder will redirect the error stream,
+ * and to actually set it, you must call {@code redirectErrorStream(boolean)}.
+ * In that case, the method should be defined like this:
+ * <pre>
+ * @CheckResult(suggest="#redirectErrorStream(boolean)")
+ * public boolean redirectErrorStream() { ... }
+ * </pre>
+ */
+ String suggest() default "";
+}
\ No newline at end of file
diff --git a/android-34/android/annotation/ChecksSdkIntAtLeast.java b/android-34/android/annotation/ChecksSdkIntAtLeast.java
new file mode 100644
index 0000000..3223125
--- /dev/null
+++ b/android-34/android/annotation/ChecksSdkIntAtLeast.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method checks if the SDK_INT API level is
+ * at least the given value, and either returns it or executes the
+ * given lambda in that case (or if it's a field, has the value true).
+ *
+ * The API level can be specified either as an API level via
+ * {@link #api()}, or for preview platforms as a codename (such as "R") via
+ * {@link #codename()}}, or it can be passed in to the method; in that
+ * case, the parameter containing the API level or code name should
+ * be specified via {@link #parameter()}, where the first parameter
+ * is numbered 0.
+ *
+ * <p>
+ * Examples:
+ * <pre><code>
+ * // Simple version check
+ * @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
+ * public static boolean isAtLeastO() {
+ * return Build.VERSION.SDK_INT >= 26;
+ * }
+ *
+ * // Required API level is passed in as first argument, and function
+ * // in second parameter is executed if SDK_INT is at least that high:
+ * @ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
+ * inline fun fromApi(value: Int, action: () -> Unit) {
+ * if (Build.VERSION.SDK_INT >= value) {
+ * action()
+ * }
+ * }
+ *
+ * // Java field:
+ * @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.LOLLIPOP)
+ * public static final boolean SUPPORTS_LETTER_SPACING =
+ * Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ *
+ * </code></pre>
+ *
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface ChecksSdkIntAtLeast {
+ /**
+ * The API level is at least the given level
+ */
+ int api() default -1;
+
+ /**
+ * The API level is at least the given codename (such as "R")
+ */
+ String codename() default "";
+
+ /**
+ * The API level is specified in the given parameter, where the first parameter is number 0
+ */
+ int parameter() default -1;
+
+ /**
+ * The parameter number for a lambda that will be executed if the API level is at least
+ * the value supplied via {@link #api()}, {@link #codename()} or
+ * {@link #parameter()}
+ */
+ int lambda() default -1;
+}
diff --git a/android-34/android/annotation/ColorInt.java b/android-34/android/annotation/ColorInt.java
new file mode 100644
index 0000000..4671b1b
--- /dev/null
+++ b/android-34/android/annotation/ColorInt.java
@@ -0,0 +1,42 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element represents a packed color
+ * int, {@code AARRGGBB}. If applied to an int array, every element
+ * in the array represents a color integer.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public abstract void setTextColor(@ColorInt int color);
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
+public @interface ColorInt {
+}
\ No newline at end of file
diff --git a/android-34/android/annotation/ColorLong.java b/android-34/android/annotation/ColorLong.java
new file mode 100644
index 0000000..9b19c76
--- /dev/null
+++ b/android-34/android/annotation/ColorLong.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * <p>Denotes that the annotated element represents a packed color
+ * long. If applied to a long array, every element in the array
+ * represents a color long. For more information on how colors
+ * are packed in a long, please refer to the documentation of
+ * the {@link android.graphics.Color} class.</p>
+ *
+ * <p>Example:</p>
+ *
+ * <pre>{@code
+ * public void setFillColor(@ColorLong long color);
+ * }</pre>
+ *
+ * @see android.graphics.Color
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
+public @interface ColorLong {
+}
diff --git a/android-34/android/annotation/ColorRes.java b/android-34/android/annotation/ColorRes.java
new file mode 100644
index 0000000..061faa0
--- /dev/null
+++ b/android-34/android/annotation/ColorRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a color resource reference (e.g. {@link android.R.color#black}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ColorRes {
+}
diff --git a/android-34/android/annotation/Condemned.java b/android-34/android/annotation/Condemned.java
new file mode 100644
index 0000000..186409b
--- /dev/null
+++ b/android-34/android/annotation/Condemned.java
@@ -0,0 +1,44 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * A program element annotated @Condemned is one that programmers are
+ * blocked from using, typically because it's about to be completely destroyed.
+ * <p>
+ * This is a stronger version of @Deprecated, and it's typically used to
+ * mark APIs that only existed temporarily in a preview SDK, and which only
+ * continue to exist temporarily to support binary compatibility.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
+public @interface Condemned {
+}
diff --git a/android-34/android/annotation/CurrentTimeMillisLong.java b/android-34/android/annotation/CurrentTimeMillisLong.java
new file mode 100644
index 0000000..355bb5a
--- /dev/null
+++ b/android-34/android/annotation/CurrentTimeMillisLong.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @paramDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @returnDoc Value is a non-negative timestamp measured as the number of
+ * milliseconds since 1970-01-01T00:00:00Z.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface CurrentTimeMillisLong {
+}
diff --git a/android-34/android/annotation/CurrentTimeSecondsLong.java b/android-34/android/annotation/CurrentTimeSecondsLong.java
new file mode 100644
index 0000000..2b4ffd7
--- /dev/null
+++ b/android-34/android/annotation/CurrentTimeSecondsLong.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp measured as the number of
+ * seconds since 1970-01-01T00:00:00Z.
+ * @paramDoc Value is a non-negative timestamp measured as the number of
+ * seconds since 1970-01-01T00:00:00Z.
+ * @returnDoc Value is a non-negative timestamp measured as the number of
+ * seconds since 1970-01-01T00:00:00Z.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface CurrentTimeSecondsLong {
+}
diff --git a/android-34/android/annotation/DeprecatedForSdk.java b/android-34/android/annotation/DeprecatedForSdk.java
new file mode 100644
index 0000000..bbd0a2c
--- /dev/null
+++ b/android-34/android/annotation/DeprecatedForSdk.java
@@ -0,0 +1,54 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * The annotated element is considered deprecated in the public SDK. This will be turned into a
+ * plain @Deprecated annotation in the SDK.
+ *
+ * <p>The value parameter should be the message to include in the documentation as a @deprecated
+ * comment.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
+public @interface DeprecatedForSdk {
+ /**
+ * The message to include in the documentation, which will be merged in as a @deprecated
+ * tag.
+ */
+ String value();
+
+ /**
+ * If specified, one or more annotation classes corresponding to particular API surfaces where
+ * the API will <b>not</b> be marked as deprecated, such as {@link SystemApi} or {@link
+ * TestApi}.
+ */
+ Class<?>[] allowIn() default {};
+}
diff --git a/android-34/android/annotation/DimenRes.java b/android-34/android/annotation/DimenRes.java
new file mode 100644
index 0000000..02ae00c
--- /dev/null
+++ b/android-34/android/annotation/DimenRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DimenRes {
+}
diff --git a/android-34/android/annotation/Dimension.java b/android-34/android/annotation/Dimension.java
new file mode 100644
index 0000000..5f705ad
--- /dev/null
+++ b/android-34/android/annotation/Dimension.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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a numeric parameter, field or method return value is expected
+ * to represent a dimension.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
+public @interface Dimension {
+ @Unit
+ int unit() default PX;
+
+ int DP = 0;
+ int PX = 1;
+ int SP = 2;
+
+ @IntDef({PX, DP, SP})
+ @Retention(SOURCE)
+ @interface Unit {}
+}
diff --git a/android-34/android/annotation/Discouraged.java b/android-34/android/annotation/Discouraged.java
new file mode 100644
index 0000000..d4e4dfc
--- /dev/null
+++ b/android-34/android/annotation/Discouraged.java
@@ -0,0 +1,53 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element, while not disallowed or deprecated, is one that
+ * programmers are generally discouraged from using.
+ * <p>
+ * Example:
+ * <pre><code>
+ * @Discouraged(message = "It is much more efficient to retrieve "
+ * + "resources by identifier than by name.")
+ * public void getValue(String name) {
+ * ...
+ * }
+ * </code></pre>
+ * </p>
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({CONSTRUCTOR, FIELD, METHOD, PARAMETER, TYPE})
+public @interface Discouraged {
+ /**
+ * Defines the message to display when an element marked with this annotation is used. An
+ * alternative should be provided in the message.
+ */
+ String message();
+}
+
diff --git a/android-34/android/annotation/DisplayContext.java b/android-34/android/annotation/DisplayContext.java
new file mode 100644
index 0000000..c2bfa7d
--- /dev/null
+++ b/android-34/android/annotation/DisplayContext.java
@@ -0,0 +1,54 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes a {@link android.content.Context} that is tied to a {@link android.view.Display} and can
+ * be used to obtain one via {@link android.content.Context#getDisplay}, but <b>may not</b> be able
+ * to obtain {@link android.view.WindowManager}, {@link android.view.LayoutInflater} or
+ * {@link android.app.WallpaperManager} via
+ * {@link android.content.Context#getSystemService(String)}. If the UI services mentioned above are
+ * required, please use contexts which are marked as {@link UiContext}.
+ * <p>
+ * {@link android.app.Activity}, and the result of
+ * {@link android.content.Context#createWindowContext(int, android.os.Bundle)} or
+ * {@link android.content.Context#createDisplayContext(android.view.Display)} can be used where a
+ * {@link DisplayContext} is required.
+ * <p>
+ * This is a marker annotation and has no specific attributes.
+ *
+ * @see android.content.Context#getDisplay()
+ * @see android.content.Context#getSystemService(String)
+ * @see android.content.Context#getSystemService(Class)
+ * @see android.content.Context#createDisplayContext(Display)
+ * @see android.content.Context#createWindowContext(int, Bundle)
+ * @see UiContext
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE, METHOD, PARAMETER, FIELD})
+public @interface DisplayContext {
+}
diff --git a/android-34/android/annotation/DrawableRes.java b/android-34/android/annotation/DrawableRes.java
new file mode 100644
index 0000000..ebefa1d
--- /dev/null
+++ b/android-34/android/annotation/DrawableRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DrawableRes {
+}
diff --git a/android-34/android/annotation/DurationMillisLong.java b/android-34/android/annotation/DurationMillisLong.java
new file mode 100644
index 0000000..ce77532
--- /dev/null
+++ b/android-34/android/annotation/DurationMillisLong.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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative duration in milliseconds.
+ * @paramDoc Value is a non-negative duration in milliseconds.
+ * @returnDoc Value is a non-negative duration in milliseconds.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DurationMillisLong {
+}
diff --git a/android-34/android/annotation/ElapsedRealtimeLong.java b/android-34/android/annotation/ElapsedRealtimeLong.java
new file mode 100644
index 0000000..9492415
--- /dev/null
+++ b/android-34/android/annotation/ElapsedRealtimeLong.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp in the
+ * {@link android.os.SystemClock#elapsedRealtime()} time base.
+ * @paramDoc Value is a non-negative timestamp in the
+ * {@link android.os.SystemClock#elapsedRealtime()} time base.
+ * @returnDoc Value is a non-negative timestamp in the
+ * {@link android.os.SystemClock#elapsedRealtime()} time base.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ElapsedRealtimeLong {
+}
diff --git a/android-34/android/annotation/EnforcePermission.java b/android-34/android/annotation/EnforcePermission.java
new file mode 100644
index 0000000..cebd669
--- /dev/null
+++ b/android-34/android/annotation/EnforcePermission.java
@@ -0,0 +1,88 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element enforces one or more permissions.
+ * <p/>
+ * Example of enforcing a single permission:
+ * <pre>{@code
+ * {@literal @}EnforcePermission(Manifest.permission.SET_WALLPAPER)
+ * public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+ *
+ * {@literal @}EnforcePermission(ACCESS_COARSE_LOCATION)
+ * public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of enforcing at least one permission from a set:
+ * <pre>{@code
+ * {@literal @}EnforcePermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ * public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of enforcing multiple permissions:
+ * <pre>{@code
+ * {@literal @}EnforcePermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ * public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * <p>
+ * This annotation should be applied to AIDL method definitions that you want
+ * to protect with permissions. Inside the class that inherits from the
+ * generated Stub class, in the corresponding method implementation, the first
+ * instruction must be a call to the auxiliary method generated by the AIDL
+ * compiler. The auxiliary will be named {@code methodName_enforcePermission}.
+ * A linter ensures that this method is called when required.
+ * </p><p>
+ * <b>Warning:</b>In Android {@link android.os.Build.VERSION_CODES#TIRAMISU},
+ * it should only be used for methods that are only called remotely, never
+ * locally (see b/241171714).
+ * </p>
+ *
+ * @see RequiresPermission
+ * @see RequiresNoPermission
+ * @hide
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface EnforcePermission {
+ /**
+ * The name of the permission that is required, if precisely one permission
+ * is required. If more than one permission is required, specify either
+ * {@link #allOf()} or {@link #anyOf()} instead.
+ * <p>
+ * If specified, {@link #anyOf()} and {@link #allOf()} must both be null.
+ */
+ String value() default "";
+
+ /**
+ * Specifies a list of permission names that are all required.
+ * <p>
+ * If specified, {@link #anyOf()} and {@link #value()} must both be null.
+ */
+ String[] allOf() default {};
+
+ /**
+ * Specifies a list of permission names where at least one is required
+ * <p>
+ * If specified, {@link #allOf()} and {@link #value()} must both be null.
+ */
+ String[] anyOf() default {};
+}
diff --git a/android-34/android/annotation/FlaggedApi.java b/android-34/android/annotation/FlaggedApi.java
new file mode 100644
index 0000000..f9f07cf
--- /dev/null
+++ b/android-34/android/annotation/FlaggedApi.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API can be made hidden or public based on decisions in build time.
+ * </p>
+ * This annotation should only appear on API that are already public and not marked
+ * <pre>@hide</pre>.
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface FlaggedApi {}
diff --git a/android-34/android/annotation/FloatRange.java b/android-34/android/annotation/FloatRange.java
new file mode 100644
index 0000000..05b5168
--- /dev/null
+++ b/android-34/android/annotation/FloatRange.java
@@ -0,0 +1,55 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element should be a float or double in the given range
+ * <p>
+ * Example:
+ * <pre><code>
+ * @FloatRange(from=0.0,to=1.0)
+ * public float getAlpha() {
+ * ...
+ * }
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+public @interface FloatRange {
+ /** Smallest value. Whether it is inclusive or not is determined
+ * by {@link #fromInclusive} */
+ double from() default Double.NEGATIVE_INFINITY;
+ /** Largest value. Whether it is inclusive or not is determined
+ * by {@link #toInclusive} */
+ double to() default Double.POSITIVE_INFINITY;
+
+ /** Whether the from value is included in the range */
+ boolean fromInclusive() default true;
+
+ /** Whether the to value is included in the range */
+ boolean toInclusive() default true;
+}
\ No newline at end of file
diff --git a/android-34/android/annotation/FontRes.java b/android-34/android/annotation/FontRes.java
new file mode 100644
index 0000000..dbacb58
--- /dev/null
+++ b/android-34/android/annotation/FontRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a Font resource reference (e.g. R.font.myfont).
+ *
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface FontRes {
+}
diff --git a/android-34/android/annotation/FractionRes.java b/android-34/android/annotation/FractionRes.java
new file mode 100644
index 0000000..fd84d3e
--- /dev/null
+++ b/android-34/android/annotation/FractionRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a fraction resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface FractionRes {
+}
diff --git a/android-34/android/annotation/HalfFloat.java b/android-34/android/annotation/HalfFloat.java
new file mode 100644
index 0000000..256008c
--- /dev/null
+++ b/android-34/android/annotation/HalfFloat.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * <p>Denotes that the annotated element represents a half-precision floating point
+ * value. Such values are stored in short data types and can be manipulated with
+ * the {@link android.util.Half} class. If applied to an array of short, every
+ * element in the array represents a half-precision float.</p>
+ *
+ * <p>Example:</p>
+ *
+ * <pre>{@code
+ * public abstract void setPosition(@HalfFloat short x, @HalfFloat short y, @HalfFloat short z);
+ * }</pre>
+ *
+ * @see android.util.Half
+ * @see android.util.Half#toHalf(float)
+ * @see android.util.Half#toFloat(short)
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface HalfFloat {
+}
diff --git a/android-34/android/annotation/Hide.java b/android-34/android/annotation/Hide.java
new file mode 100644
index 0000000..c8e5a4a
--- /dev/null
+++ b/android-34/android/annotation/Hide.java
@@ -0,0 +1,41 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that an API is hidden by default, in a similar fashion to the
+ * <pre>@hide</pre> javadoc tag.
+ *
+ * <p>Note that, in order for this to work, metalava has to be invoked with
+ * the flag {@code --hide-annotation android.annotation.Hide}.
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.CLASS)
+public @interface Hide {
+}
diff --git a/android-34/android/annotation/IdRes.java b/android-34/android/annotation/IdRes.java
new file mode 100644
index 0000000..b286965
--- /dev/null
+++ b/android-34/android/annotation/IdRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an id resource reference (e.g. {@link android.R.id#copy}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IdRes {
+}
diff --git a/android-34/android/annotation/IntDef.java b/android-34/android/annotation/IntDef.java
new file mode 100644
index 0000000..f84a676
--- /dev/null
+++ b/android-34/android/annotation/IntDef.java
@@ -0,0 +1,64 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element of integer type, represents
+ * a logical type and that its value should be one of the explicitly
+ * named constants. If the {@link #flag()} attribute is set to true,
+ * multiple constants can be combined.
+ * <p>
+ * <pre><code>
+ * @Retention(SOURCE)
+ * @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * public @interface NavigationMode {}
+ * public static final int NAVIGATION_MODE_STANDARD = 0;
+ * public static final int NAVIGATION_MODE_LIST = 1;
+ * public static final int NAVIGATION_MODE_TABS = 2;
+ * ...
+ * public abstract void setNavigationMode(@NavigationMode int mode);
+ * @NavigationMode
+ * public abstract int getNavigationMode();
+ * </code></pre>
+ * For a flag, set the flag attribute:
+ * <pre><code>
+ * @IntDef(
+ * flag = true,
+ * value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface IntDef {
+ /** Defines the constant prefix for this element */
+ String[] prefix() default {};
+ /** Defines the constant suffix for this element */
+ String[] suffix() default {};
+
+ /** Defines the allowed constants for this element */
+ int[] value() default {};
+
+ /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
+ boolean flag() default false;
+}
diff --git a/android-34/android/annotation/IntRange.java b/android-34/android/annotation/IntRange.java
new file mode 100644
index 0000000..c043e2d
--- /dev/null
+++ b/android-34/android/annotation/IntRange.java
@@ -0,0 +1,48 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element should be an int or long in the given range
+ * <p>
+ * Example:
+ * <pre><code>
+ * @IntRange(from=0,to=255)
+ * public int getAlpha() {
+ * ...
+ * }
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
+public @interface IntRange {
+ /** Smallest value, inclusive */
+ long from() default Long.MIN_VALUE;
+ /** Largest value, inclusive */
+ long to() default Long.MAX_VALUE;
+}
\ No newline at end of file
diff --git a/android-34/android/annotation/IntegerRes.java b/android-34/android/annotation/IntegerRes.java
new file mode 100644
index 0000000..5313f4a
--- /dev/null
+++ b/android-34/android/annotation/IntegerRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IntegerRes {
+}
diff --git a/android-34/android/annotation/InterpolatorRes.java b/android-34/android/annotation/InterpolatorRes.java
new file mode 100644
index 0000000..8877a5f
--- /dev/null
+++ b/android-34/android/annotation/InterpolatorRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an interpolator resource reference (e.g. {@link android.R.interpolator#cycle}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface InterpolatorRes {
+}
diff --git a/android-34/android/annotation/LayoutRes.java b/android-34/android/annotation/LayoutRes.java
new file mode 100644
index 0000000..15ba86f
--- /dev/null
+++ b/android-34/android/annotation/LayoutRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a layout resource reference (e.g. {@link android.R.layout#list_content}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface LayoutRes {
+}
diff --git a/android-34/android/annotation/LongDef.java b/android-34/android/annotation/LongDef.java
new file mode 100644
index 0000000..8723eef
--- /dev/null
+++ b/android-34/android/annotation/LongDef.java
@@ -0,0 +1,62 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated long element represents
+ * a logical type and that its value should be one of the explicitly
+ * named constants. If the {@link #flag()} attribute is set to true,
+ * multiple constants can be combined.
+ * <p>
+ * <pre><code>
+ * @Retention(SOURCE)
+ * @LongDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * public @interface NavigationMode {}
+ * public static final long NAVIGATION_MODE_STANDARD = 0;
+ * public static final long NAVIGATION_MODE_LIST = 1;
+ * public static final long NAVIGATION_MODE_TABS = 2;
+ * ...
+ * public abstract void setNavigationMode(@NavigationMode long mode);
+ * @NavigationMode
+ * public abstract long getNavigationMode();
+ * </code></pre>
+ * For a flag, set the flag attribute:
+ * <pre><code>
+ * @LongDef(
+ * flag = true,
+ * value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface LongDef {
+ /** Defines the constant prefix for this element */
+ String[] prefix() default "";
+
+ /** Defines the allowed constants for this element */
+ long[] value() default {};
+
+ /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
+ boolean flag() default false;
+}
diff --git a/android-34/android/annotation/MainThread.java b/android-34/android/annotation/MainThread.java
new file mode 100644
index 0000000..a070246
--- /dev/null
+++ b/android-34/android/annotation/MainThread.java
@@ -0,0 +1,47 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method should only be called on the main thread.
+ * If the annotated element is a class, then all methods in the class should be
+ * called on the main thread.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @MainThread
+ * public void deliverResult(D data) { ... }
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method must be called from the main thread of your app.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface MainThread {
+}
diff --git a/android-34/android/annotation/MenuRes.java b/android-34/android/annotation/MenuRes.java
new file mode 100644
index 0000000..b6dcc46
--- /dev/null
+++ b/android-34/android/annotation/MenuRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a menu resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface MenuRes {
+}
diff --git a/android-34/android/annotation/NavigationRes.java b/android-34/android/annotation/NavigationRes.java
new file mode 100644
index 0000000..3af5ecf
--- /dev/null
+++ b/android-34/android/annotation/NavigationRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a navigation resource reference (e.g. {@code R.navigation.flow}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NavigationRes {
+}
diff --git a/android-34/android/annotation/NonNull.java b/android-34/android/annotation/NonNull.java
new file mode 100644
index 0000000..20472ba
--- /dev/null
+++ b/android-34/android/annotation/NonNull.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a parameter, field or method return value can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value cannot be {@code null}.
+ * @returnDoc This value cannot be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NonNull {
+}
diff --git a/android-34/android/annotation/NonUiContext.java b/android-34/android/annotation/NonUiContext.java
new file mode 100644
index 0000000..5d26850
--- /dev/null
+++ b/android-34/android/annotation/NonUiContext.java
@@ -0,0 +1,45 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes a {@link android.content.Context} that <b>can not</b> be used to obtain a
+ * {@link android.view.Display} via {@link android.content.Context#getDisplay} nor to obtain a
+ * {@link android.view.WindowManager}, {@link android.view.LayoutInflater} or
+ * {@link android.app.WallpaperManager} via
+ * {@link android.content.Context#getSystemService(String)}.
+ * <p>
+ * This is a marker annotation and has no specific attributes.
+ *
+ * @see android.content.Context#getDisplay()
+ * @see android.content.Context#getSystemService(String)
+ * @see android.content.Context#getSystemService(Class)
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE, METHOD, PARAMETER, FIELD})
+public @interface NonUiContext {
+}
diff --git a/android-34/android/annotation/Nullable.java b/android-34/android/annotation/Nullable.java
new file mode 100644
index 0000000..b8473e7
--- /dev/null
+++ b/android-34/android/annotation/Nullable.java
@@ -0,0 +1,45 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a parameter, field or method return value can be null.
+ * <p>
+ * When decorating a method call parameter, this denotes that the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically
+ * used on optional parameters.
+ * <p>
+ * When decorating a method, this denotes the method might legitimately return
+ * null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value may be {@code null}.
+ * @returnDoc This value may be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface Nullable {
+}
diff --git a/android-34/android/annotation/PermissionMethod.java b/android-34/android/annotation/PermissionMethod.java
new file mode 100644
index 0000000..91fafcc
--- /dev/null
+++ b/android-34/android/annotation/PermissionMethod.java
@@ -0,0 +1,52 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Documents that the subject method's job is to look
+ * up whether the provided or calling uid/pid has the requested permission.
+ *
+ * <p>Methods should either return `void`, but potentially throw {@link SecurityException},
+ * or return {@link android.content.pm.PackageManager.PermissionResult} `int`.
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface PermissionMethod {
+ /**
+ * Hard-coded list of permissions checked by this method
+ */
+ @PermissionName String[] value() default {};
+ /**
+ * If true, the check passes if the caller
+ * has any ONE of the supplied permissions
+ */
+ boolean anyOf() default false;
+ /**
+ * Signifies that the permission check passes if
+ * the calling process OR the current process has
+ * the permission
+ */
+ boolean orSelf() default false;
+}
diff --git a/android-34/android/annotation/PermissionName.java b/android-34/android/annotation/PermissionName.java
new file mode 100644
index 0000000..2b9f525
--- /dev/null
+++ b/android-34/android/annotation/PermissionName.java
@@ -0,0 +1,35 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated {@link String} represents a permission name.
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface PermissionName {}
diff --git a/android-34/android/annotation/PluralsRes.java b/android-34/android/annotation/PluralsRes.java
new file mode 100644
index 0000000..31ac729
--- /dev/null
+++ b/android-34/android/annotation/PluralsRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a plurals resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface PluralsRes {
+}
diff --git a/android-34/android/annotation/PropagateAllowBlocking.java b/android-34/android/annotation/PropagateAllowBlocking.java
new file mode 100644
index 0000000..75ff079
--- /dev/null
+++ b/android-34/android/annotation/PropagateAllowBlocking.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used in AIDL files to annotate a method whose return value should inherit
+ * the {@code android.os.Binder#allowBlocking()} flag the object which the method is called on.
+ *
+ * (This annotation is only used inside aidl files, and will not be used in the generated java
+ * files, so technically we do not need this annotation class, but it's added just for cleanliness.)
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface PropagateAllowBlocking {
+}
diff --git a/android-34/android/annotation/Px.java b/android-34/android/annotation/Px.java
new file mode 100644
index 0000000..cec7f80
--- /dev/null
+++ b/android-34/android/annotation/Px.java
@@ -0,0 +1,42 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a numeric parameter, field or method return value is expected
+ * to represent a pixel dimension.
+ *
+ * @memberDoc This units of this value are pixels.
+ * @paramDoc This units of this value are pixels.
+ * @returnDoc This units of this value are pixels.
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+@Dimension(unit = Dimension.PX)
+public @interface Px {
+}
diff --git a/android-34/android/annotation/RawRes.java b/android-34/android/annotation/RawRes.java
new file mode 100644
index 0000000..39970b3
--- /dev/null
+++ b/android-34/android/annotation/RawRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a raw resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface RawRes {
+}
diff --git a/android-34/android/annotation/RequiresApi.java b/android-34/android/annotation/RequiresApi.java
new file mode 100644
index 0000000..2b857cc
--- /dev/null
+++ b/android-34/android/annotation/RequiresApi.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element should only be called on the given API level or higher.
+ *
+ * <p>This is similar in purpose to the older {@code @TargetApi} annotation, but more clearly
+ * expresses that this is a requirement on the caller, rather than being used to "suppress" warnings
+ * within the method that exceed the {@code minSdkVersion}.
+ *
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE})
+public @interface RequiresApi {
+ /**
+ * The API level to require. Alias for {@link #api} which allows you to leave out the {@code
+ * api=} part.
+ */
+ @IntRange(from = 1)
+ int value() default 1;
+
+ /** The API level to require */
+ @IntRange(from = 1)
+ int api() default 1;
+}
diff --git a/android-34/android/annotation/RequiresFeature.java b/android-34/android/annotation/RequiresFeature.java
new file mode 100644
index 0000000..9236700
--- /dev/null
+++ b/android-34/android/annotation/RequiresFeature.java
@@ -0,0 +1,51 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element requires one or more device features. This
+ * is used to auto-generate documentation.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE,FIELD,METHOD,CONSTRUCTOR})
+public @interface RequiresFeature {
+ /**
+ * The name of the device feature that is required.
+ */
+ String value();
+
+ /**
+ * Defines the name of the method that should be called to check whether the feature is
+ * available, using the same signature format as javadoc. The feature checking method can have
+ * multiple parameters, but the feature name parameter must be of type String and must also be
+ * the first String-type parameter.
+ * <p>
+ * By default, the enforcement is
+ * {@link android.content.pm.PackageManager#hasSystemFeature(String)}.
+ */
+ String enforcement() default("android.content.pm.PackageManager#hasSystemFeature");
+}
diff --git a/android-34/android/annotation/RequiresNoPermission.java b/android-34/android/annotation/RequiresNoPermission.java
new file mode 100644
index 0000000..cdbf36e
--- /dev/null
+++ b/android-34/android/annotation/RequiresNoPermission.java
@@ -0,0 +1,48 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element requires no permissions.
+ * <p>
+ * This explicit annotation helps distinguish which of three states that an
+ * element may exist in:
+ * <ul>
+ * <li>Annotated with {@link RequiresPermission}, indicating that an element
+ * requires (or may require) one or more permissions.
+ * <li>Annotated with {@link RequiresNoPermission}, indicating that an element
+ * requires no permissions.
+ * <li>Neither annotation, indicating that no explicit declaration about
+ * permissions has been made for that element.
+ * </ul>
+ *
+ * @see RequiresPermission
+ * @hide
+ */
+@Retention(CLASS)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresNoPermission {
+}
diff --git a/android-34/android/annotation/RequiresPermission.java b/android-34/android/annotation/RequiresPermission.java
new file mode 100644
index 0000000..0379d30
--- /dev/null
+++ b/android-34/android/annotation/RequiresPermission.java
@@ -0,0 +1,137 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element requires (or may require) one or more permissions.
+ * <p/>
+ * Example of requiring a single permission:
+ * <pre>{@code
+ * {@literal @}RequiresPermission(Manifest.permission.SET_WALLPAPER)
+ * public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+ *
+ * {@literal @}RequiresPermission(ACCESS_COARSE_LOCATION)
+ * public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of requiring at least one permission from a set:
+ * <pre>{@code
+ * {@literal @}RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ * public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of requiring multiple permissions:
+ * <pre>{@code
+ * {@literal @}RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ * public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of requiring separate read and write permissions for a content provider:
+ * <pre>{@code
+ * {@literal @}RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ * {@literal @}RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ * public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * }</pre>
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter. For example, consider
+ * {@link android.app.Activity#startActivity(android.content.Intent)
+ * Activity#startActivity(Intent)}:
+ * <pre>{@code
+ * public void startActivity(@RequiresPermission Intent intent) { ... }
+ * }</pre>
+ * Notice how there are no actual permission names listed in the annotation. The actual
+ * permissions required will depend on the particular intent passed in. For example,
+ * the code may look like this:
+ * <pre>{@code
+ * Intent intent = new Intent(Intent.ACTION_CALL);
+ * startActivity(intent);
+ * }</pre>
+ * and the actual permission requirement for this particular intent is described on
+ * the Intent name itself:
+ * <pre>{@code
+ * {@literal @}RequiresPermission(Manifest.permission.CALL_PHONE)
+ * public static final String ACTION_CALL = "android.intent.action.CALL";
+ * }</pre>
+ *
+ * @see RequiresNoPermission
+ * @hide
+ */
+@Retention(CLASS)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresPermission {
+ /**
+ * The name of the permission that is required, if precisely one permission
+ * is required. If more than one permission is required, specify either
+ * {@link #allOf()} or {@link #anyOf()} instead.
+ * <p>
+ * If specified, {@link #anyOf()} and {@link #allOf()} must both be null.
+ */
+ String value() default "";
+
+ /**
+ * Specifies a list of permission names that are all required.
+ * <p>
+ * If specified, {@link #anyOf()} and {@link #value()} must both be null.
+ */
+ String[] allOf() default {};
+
+ /**
+ * Specifies a list of permission names where at least one is required
+ * <p>
+ * If specified, {@link #allOf()} and {@link #value()} must both be null.
+ */
+ String[] anyOf() default {};
+
+ /**
+ * If true, the permission may not be required in all cases (e.g. it may only be
+ * enforced on certain platforms, or for certain call parameters, etc.
+ */
+ boolean conditional() default false;
+
+ /**
+ * Specifies that the given permission is required for read operations.
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter (and typically
+ * the corresponding field passed in will be one of a set of constants which have
+ * been annotated with a <code>@RequiresPermission</code> annotation.)
+ */
+ @Target({FIELD, METHOD, PARAMETER})
+ @interface Read {
+ RequiresPermission value() default @RequiresPermission;
+ }
+
+ /**
+ * Specifies that the given permission is required for write operations.
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter (and typically
+ * the corresponding field passed in will be one of a set of constants which have
+ * been annotated with a <code>@RequiresPermission</code> annotation.)
+ */
+ @Target({FIELD, METHOD, PARAMETER})
+ @interface Write {
+ RequiresPermission value() default @RequiresPermission;
+ }
+}
diff --git a/android-34/android/annotation/SdkConstant.java b/android-34/android/annotation/SdkConstant.java
new file mode 100644
index 0000000..0a53186
--- /dev/null
+++ b/android-34/android/annotation/SdkConstant.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a constant field value should be exported to be used in the SDK tools.
+ * @hide
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.SOURCE)
+public @interface SdkConstant {
+ public static enum SdkConstantType {
+ ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE;
+ }
+
+ SdkConstantType value();
+}
diff --git a/android-34/android/annotation/Size.java b/android-34/android/annotation/Size.java
new file mode 100644
index 0000000..7c3e70f
--- /dev/null
+++ b/android-34/android/annotation/Size.java
@@ -0,0 +1,52 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element should have a given size or length.
+ * Note that "-1" means "unset". Typically used with a parameter or
+ * return value of type array or collection.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public void getLocationInWindow(@Size(2) int[] location) {
+ * ...
+ * }
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD})
+public @interface Size {
+ /** An exact size (or -1 if not specified) */
+ long value() default -1;
+ /** A minimum size, inclusive */
+ long min() default Long.MIN_VALUE;
+ /** A maximum size, inclusive */
+ long max() default Long.MAX_VALUE;
+ /** The size must be a multiple of this factor */
+ long multiple() default 1;
+}
\ No newline at end of file
diff --git a/android-34/android/annotation/StringDef.java b/android-34/android/annotation/StringDef.java
new file mode 100644
index 0000000..a37535b
--- /dev/null
+++ b/android-34/android/annotation/StringDef.java
@@ -0,0 +1,56 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated String element, represents a logical
+ * type and that its value should be one of the explicitly named constants.
+ * <p>
+ * Example:
+ * <pre><code>
+ * @Retention(SOURCE)
+ * @StringDef({
+ * POWER_SERVICE,
+ * WINDOW_SERVICE,
+ * LAYOUT_INFLATER_SERVICE
+ * })
+ * public @interface ServiceName {}
+ * public static final String POWER_SERVICE = "power";
+ * public static final String WINDOW_SERVICE = "window";
+ * public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+ * ...
+ * public abstract Object getSystemService(@ServiceName String name);
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface StringDef {
+ /** Defines the constant prefix for this element */
+ String[] prefix() default {};
+ /** Defines the constant suffix for this element */
+ String[] suffix() default {};
+
+ /** Defines the allowed constants for this element */
+ String[] value() default {};
+}
diff --git a/android-34/android/annotation/StringRes.java b/android-34/android/annotation/StringRes.java
new file mode 100644
index 0000000..190b68a
--- /dev/null
+++ b/android-34/android/annotation/StringRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a String resource reference (e.g. {@link android.R.string#ok}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StringRes {
+}
diff --git a/android-34/android/annotation/StyleRes.java b/android-34/android/annotation/StyleRes.java
new file mode 100644
index 0000000..4453b8d
--- /dev/null
+++ b/android-34/android/annotation/StyleRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a integer parameter, field or method return value is expected
+ * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StyleRes {
+}
diff --git a/android-34/android/annotation/StyleableRes.java b/android-34/android/annotation/StyleableRes.java
new file mode 100644
index 0000000..3c1895e
--- /dev/null
+++ b/android-34/android/annotation/StyleableRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a integer parameter, field or method return value is expected
+ * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StyleableRes {
+}
diff --git a/android-34/android/annotation/SupportsCoexistence.java b/android-34/android/annotation/SupportsCoexistence.java
new file mode 100644
index 0000000..8e35bd8
--- /dev/null
+++ b/android-34/android/annotation/SupportsCoexistence.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that this policy API can be set by multiple admins.
+ *
+ * <p>Starting from Android U ({@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}), multiple management
+ * admins will be able to coexist on the same device (e.g. a financed device with an enterprise
+ * admin). This requires adding multi-admin support for all the policy-related APIs (mostly in
+ * {@link android.app.admin.DevicePolicyManager}). However, for Android U only a subset of
+ * APIs will be supported, those will be marked with {@literal @}SupportsCoexistence annotation.
+ * </p>
+ *
+ * Example:
+ * <pre>{@code
+ * {@literal @}SupportsCoexistence
+ * public boolean setPermissionGrantState({@literal @}NonNull ComponentName admin,
+ * {@literal @}NonNull String packageName, {@literal @}NonNull String permission,
+ * {@literal @}PermissionGrantState int grantState) {
+ * throwIfParentInstance("setPermissionGrantState");
+ * }</pre>
+ *
+ * @hide
+ */
+
+@Retention(SOURCE)
+@Target(METHOD)
+public @interface SupportsCoexistence {
+}
diff --git a/android-34/android/annotation/SuppressAutoDoc.java b/android-34/android/annotation/SuppressAutoDoc.java
new file mode 100644
index 0000000..e34e03b
--- /dev/null
+++ b/android-34/android/annotation/SuppressAutoDoc.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that any automatically generated documentation should be suppressed
+ * for the annotated method, parameter, or field.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+public @interface SuppressAutoDoc {
+}
diff --git a/android-34/android/annotation/SuppressLint.java b/android-34/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/android-34/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ /**
+ * The set of warnings (identified by the lint issue id) that should be
+ * ignored by lint. It is not an error to specify an unrecognized name.
+ */
+ String[] value();
+}
diff --git a/android-34/android/annotation/SystemApi.java b/android-34/android/annotation/SystemApi.java
new file mode 100644
index 0000000..a468439
--- /dev/null
+++ b/android-34/android/annotation/SystemApi.java
@@ -0,0 +1,80 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is exposed for use by bundled system applications.
+ * <p>
+ * These APIs are not guaranteed to remain consistent release-to-release,
+ * and are not for use by apps linking against the Android SDK.
+ * </p><p>
+ * This annotation should only appear on API that is already marked <pre>@hide</pre>.
+ * </p>
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SystemApi {
+ enum Client {
+ /**
+ * Specifies that the intended clients of a SystemApi are privileged apps.
+ * This is the default value for {@link #client}.
+ */
+ PRIVILEGED_APPS,
+
+ /**
+ * Specifies that the intended clients of a SystemApi are used by classes in
+ * <pre>BOOTCLASSPATH</pre> in mainline modules. Mainline modules can also expose
+ * this type of system APIs too when they're used only by the non-updatable
+ * platform code.
+ */
+ MODULE_LIBRARIES,
+
+ /**
+ * Specifies that the system API is available only in the system server process.
+ * Use this to expose APIs from code loaded by the system server process <em>but</em>
+ * not in <pre>BOOTCLASSPATH</pre>.
+ */
+ SYSTEM_SERVER
+ }
+
+ /**
+ * The intended client of this SystemAPI.
+ */
+ Client client() default android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+ /**
+ * Container for {@link SystemApi} that allows it to be applied repeatedly to types.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(TYPE)
+ @interface Container {
+ SystemApi[] value();
+ }
+}
diff --git a/android-34/android/annotation/SystemService.java b/android-34/android/annotation/SystemService.java
new file mode 100644
index 0000000..c05c1ba
--- /dev/null
+++ b/android-34/android/annotation/SystemService.java
@@ -0,0 +1,42 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Description of a system service available through
+ * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate
+ * documentation explaining how to obtain a reference to the service.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface SystemService {
+ /**
+ * The string name of the system service that can be passed to
+ * {@link android.content.Context#getSystemService(String)}.
+ *
+ * @see android.content.Context#getSystemServiceName(Class)
+ */
+ String value();
+}
diff --git a/android-34/android/annotation/TargetApi.java b/android-34/android/annotation/TargetApi.java
new file mode 100644
index 0000000..975318e
--- /dev/null
+++ b/android-34/android/annotation/TargetApi.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should treat this type as targeting a given API level, no matter what the
+ project target is. */
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetApi {
+ /**
+ * This sets the target api level for the type..
+ */
+ int value();
+}
diff --git a/android-34/android/annotation/TestApi.java b/android-34/android/annotation/TestApi.java
new file mode 100644
index 0000000..0e9ed37
--- /dev/null
+++ b/android-34/android/annotation/TestApi.java
@@ -0,0 +1,42 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is exposed for use by CTS.
+ * <p>
+ * These APIs are not guaranteed to remain consistent release-to-release,
+ * and are not for use by apps linking against the Android SDK.
+ * </p>
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface TestApi {
+}
diff --git a/android-34/android/annotation/TransitionRes.java b/android-34/android/annotation/TransitionRes.java
new file mode 100644
index 0000000..06bac74
--- /dev/null
+++ b/android-34/android/annotation/TransitionRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a transition resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface TransitionRes {
+}
diff --git a/android-34/android/annotation/UiContext.java b/android-34/android/annotation/UiContext.java
new file mode 100644
index 0000000..1bfec1d
--- /dev/null
+++ b/android-34/android/annotation/UiContext.java
@@ -0,0 +1,52 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes a {@link android.content.Context} that can be used to create UI, meaning that it can
+ * provide a {@link android.view.Display} via {@link android.content.Context#getDisplay} and can be
+ * used to obtain a {@link android.view.WindowManager}, {@link android.view.LayoutInflater} or
+ * {@link android.app.WallpaperManager} via
+ * {@link android.content.Context#getSystemService(String)}. A {@link android.content.Context}
+ * which is marked as {@link UiContext} implies that the {@link android.content.Context} is also a
+ * {@link android.view.DisplayContext}.
+ * <p>
+ * This kind of {@link android.content.Context} is usually an {@link android.app.Activity} or
+ * created via {@link android.content.Context#createWindowContext(int, android.os.Bundle)}.
+ * </p>
+ * This is a marker annotation and has no specific attributes.
+ *
+ * @see android.content.Context#getDisplay()
+ * @see android.content.Context#getSystemService(String)
+ * @see android.content.Context#getSystemService(Class)
+ * @see android.content.Context#createWindowContext(int, android.os.Bundle)
+ * @see DisplayContext
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE, METHOD, PARAMETER, FIELD})
+public @interface UiContext {
+}
diff --git a/android-34/android/annotation/UiThread.java b/android-34/android/annotation/UiThread.java
new file mode 100644
index 0000000..3f51254
--- /dev/null
+++ b/android-34/android/annotation/UiThread.java
@@ -0,0 +1,49 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method or constructor should only be called on the
+ * UI thread. If the annotated element is a class, then all methods in the class
+ * should be called on the UI thread.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @UiThread
+ * public abstract void setText(@NonNull String text) { ... }
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method must be called on the thread that originally created
+ * this UI element. This is typically the
+ * {@linkplain android.os.Looper#getMainLooper() main thread} of your app.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface UiThread {
+}
diff --git a/android-34/android/annotation/UptimeMillisLong.java b/android-34/android/annotation/UptimeMillisLong.java
new file mode 100644
index 0000000..e7a764b
--- /dev/null
+++ b/android-34/android/annotation/UptimeMillisLong.java
@@ -0,0 +1,39 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp in the
+ * {@link android.os.SystemClock#uptimeMillis()} time base.
+ * @paramDoc Value is a non-negative timestamp in the
+ * {@link android.os.SystemClock#uptimeMillis()} time base.
+ * @returnDoc Value is a non-negative timestamp in the
+ * {@link android.os.SystemClock#uptimeMillis()} time base.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface UptimeMillisLong {
+}
diff --git a/android-34/android/annotation/UserHandleAware.java b/android-34/android/annotation/UserHandleAware.java
new file mode 100644
index 0000000..60dcbd8
--- /dev/null
+++ b/android-34/android/annotation/UserHandleAware.java
@@ -0,0 +1,96 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API that uses {@code context.getUser} or {@code context.getUserId}
+ * to operate across users (as the user associated with the context)
+ * <p>
+ * To create a {@link android.content.Context} associated with a different user,
+ * use {@link android.content.Context#createContextAsUser} or
+ * {@link android.content.Context#createPackageContextAsUser}
+ * <p>
+ * Example:
+ * <pre>{@code
+ * {@literal @}UserHandleAware
+ * public abstract PackageInfo getPackageInfo({@literal @}NonNull String packageName,
+ * {@literal @}PackageInfoFlags int flags) throws NameNotFoundException;
+ * }</pre>
+ *
+ * @memberDoc This method uses {@linkplain android.content.Context#getUser}
+ * or {@linkplain android.content.Context#getUserId} to execute across users.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE, METHOD, CONSTRUCTOR, PACKAGE})
+public @interface UserHandleAware {
+
+ /**
+ * Specifies the SDK version at which this method became {@literal @}UserHandleAware,
+ * if it was not always so.
+ *
+ * Prior to this level, the method is not considered {@literal @}UserHandleAware and therefore
+ * uses the {@link android.os#myUserHandle() calling user},
+ * not the {@link android.content.Context#getUser context user}.
+ *
+ * Note that when an API marked with this parameter is run on a device whose OS predates the
+ * stated version, the calling user will be used, since on such a
+ * device, the API is not {@literal @}UserHandleAware yet.
+ */
+ int enabledSinceTargetSdkVersion() default 0;
+
+ /**
+ * Specifies the permission name required
+ * if the context user differs from the calling user.
+ *
+ * This requirement is in addition to any specified by
+ * {@link android.annotation.RequiresPermission}.
+ *
+ * @see android.annotation.RequiresPermission#value()
+ */
+ String requiresPermissionIfNotCaller() default "";
+
+ /**
+ * Specifies a list of permission names where at least one is required
+ * if the context user differs from the calling user.
+ *
+ * This requirement is in addition to any specified by
+ * {@link android.annotation.RequiresPermission}.
+ *
+ * @see android.annotation.RequiresPermission#anyOf()
+ */
+ String[] requiresAnyOfPermissionsIfNotCaller() default {};
+
+ /**
+ * Specifies a list of permission names where at least one is required if the context
+ * user is not in the same profile group as the calling user.
+ *
+ * This requirement is in addition to any specified by
+ * {@link android.annotation.RequiresPermission}.
+ *
+ * @see android.annotation.RequiresPermission#anyOf()
+ */
+ String[] requiresAnyOfPermissionsIfNotCallerProfileGroup() default {};
+}
diff --git a/android-34/android/annotation/UserIdInt.java b/android-34/android/annotation/UserIdInt.java
new file mode 100644
index 0000000..7b9ce25
--- /dev/null
+++ b/android-34/android/annotation/UserIdInt.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element is a multi-user user ID. This is
+ * <em>not</em> the same as a UID.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface UserIdInt {
+}
diff --git a/android-34/android/annotation/Widget.java b/android-34/android/annotation/Widget.java
new file mode 100644
index 0000000..6756cd7
--- /dev/null
+++ b/android-34/android/annotation/Widget.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a class is a widget usable by application developers to create UI.
+ * <p>
+ * This must be used in cases where:
+ * <ul>
+ * <li>The widget is not in the package <code>android.widget</code></li>
+ * <li>The widget extends <code>android.view.ViewGroup</code></li>
+ * </ul>
+ * @hide
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.SOURCE)
+public @interface Widget {
+}
diff --git a/android-34/android/annotation/WorkerThread.java b/android-34/android/annotation/WorkerThread.java
new file mode 100644
index 0000000..8c2a4d3
--- /dev/null
+++ b/android-34/android/annotation/WorkerThread.java
@@ -0,0 +1,48 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated method should only be called on a worker thread.
+ * If the annotated element is a class, then all methods in the class should be
+ * called on a worker thread.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ * @WorkerThread
+ * protected abstract FilterResults performFiltering(CharSequence constraint);
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method may take several seconds to complete, so it should
+ * only be called from a worker thread.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface WorkerThread {
+}
diff --git a/android-34/android/annotation/XmlRes.java b/android-34/android/annotation/XmlRes.java
new file mode 100644
index 0000000..5fb8a4a
--- /dev/null
+++ b/android-34/android/annotation/XmlRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be an XML resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface XmlRes {
+}
diff --git a/android-34/android/app/ActionBar.java b/android-34/android/app/ActionBar.java
new file mode 100644
index 0000000..504364c
--- /dev/null
+++ b/android-34/android/app/ActionBar.java
@@ -0,0 +1,1436 @@
+/*
+ * 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.app;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+import android.view.Window;
+import android.view.inspector.InspectableProperty;
+import android.widget.SpinnerAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A primary toolbar within the activity that may display the activity title, application-level
+ * navigation affordances, and other interactive items.
+ *
+ * <p>Beginning with Android 3.0 (API level 11), the action bar appears at the top of an
+ * activity's window when the activity uses the system's {@link
+ * android.R.style#Theme_Holo Holo} theme (or one of its descendant themes), which is the default.
+ * You may otherwise add the action bar by calling {@link
+ * android.view.Window#requestFeature requestFeature(FEATURE_ACTION_BAR)} or by declaring it in a
+ * custom theme with the {@link android.R.styleable#Theme_windowActionBar windowActionBar} property.
+ * </p>
+ *
+ * <p>Beginning with Android L (API level 21), the action bar may be represented by any
+ * Toolbar widget within the application layout. The application may signal to the Activity
+ * which Toolbar should be treated as the Activity's action bar. Activities that use this
+ * feature should use one of the supplied <code>.NoActionBar</code> themes, set the
+ * {@link android.R.styleable#Theme_windowActionBar windowActionBar} attribute to <code>false</code>
+ * or otherwise not request the window feature.</p>
+ *
+ * <p>By adjusting the window features requested by the theme and the layouts used for
+ * an Activity's content view, an app can use the standard system action bar on older platform
+ * releases and the newer inline toolbars on newer platform releases. The <code>ActionBar</code>
+ * object obtained from the Activity can be used to control either configuration transparently.</p>
+ *
+ * <p>When using the Holo themes the action bar shows the application icon on
+ * the left, followed by the activity title. If your activity has an options menu, you can make
+ * select items accessible directly from the action bar as "action items". You can also
+ * modify various characteristics of the action bar or remove it completely.</p>
+ *
+ * <p>When using the Material themes (default in API 21 or newer) the navigation button
+ * (formerly "Home") takes over the space previously occupied by the application icon.
+ * Apps wishing to express a stronger branding should use their brand colors heavily
+ * in the action bar and other application chrome or use a {@link #setLogo(int) logo}
+ * in place of their standard title text.</p>
+ *
+ * <p>From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link
+ * android.app.Activity#getActionBar getActionBar()}.</p>
+ *
+ * <p>In some cases, the action bar may be overlayed by another bar that enables contextual actions,
+ * using an {@link android.view.ActionMode}. For example, when the user selects one or more items in
+ * your activity, you can enable an action mode that offers actions specific to the selected
+ * items, with a UI that temporarily replaces the action bar. Although the UI may occupy the
+ * same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for
+ * {@link ActionBar}.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to use the action bar, including how to add action items, navigation
+ * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
+ * Bar</a> developer guide.</p>
+ * </div>
+ */
+public abstract class ActionBar {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NAVIGATION_MODE_" }, value = {
+ NAVIGATION_MODE_STANDARD,
+ NAVIGATION_MODE_LIST,
+ NAVIGATION_MODE_TABS
+ })
+ public @interface NavigationMode {}
+
+ /**
+ * Standard navigation mode. Consists of either a logo or icon
+ * and title text with an optional subtitle. Clicking any of these elements
+ * will dispatch onOptionsItemSelected to the host Activity with
+ * a MenuItem with item ID android.R.id.home.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public static final int NAVIGATION_MODE_STANDARD = 0;
+
+ /**
+ * List navigation mode. Instead of static title text this mode
+ * presents a list menu for navigation within the activity.
+ * e.g. this might be presented to the user as a dropdown list.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public static final int NAVIGATION_MODE_LIST = 1;
+
+ /**
+ * Tab navigation mode. Instead of static title text this mode
+ * presents a series of tabs for navigation within the activity.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public static final int NAVIGATION_MODE_TABS = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "DISPLAY_" }, value = {
+ DISPLAY_USE_LOGO,
+ DISPLAY_SHOW_HOME,
+ DISPLAY_HOME_AS_UP,
+ DISPLAY_SHOW_TITLE,
+ DISPLAY_SHOW_CUSTOM,
+ DISPLAY_TITLE_MULTIPLE_LINES
+ })
+ public @interface DisplayOptions {}
+
+ /**
+ * Use logo instead of icon if available. This flag will cause appropriate
+ * navigation modes to use a wider logo in place of the standard icon.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public static final int DISPLAY_USE_LOGO = 0x1;
+
+ /**
+ * Show 'home' elements in this action bar, leaving more space for other
+ * navigation elements. This includes logo and icon.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public static final int DISPLAY_SHOW_HOME = 0x2;
+
+ /**
+ * Display the 'home' element such that it appears as an 'up' affordance.
+ * e.g. show an arrow to the left indicating the action that will be taken.
+ *
+ * Set this flag if selecting the 'home' button in the action bar to return
+ * up by a single level in your UI rather than back to the top level or front page.
+ *
+ * <p>Setting this option will implicitly enable interaction with the home/up
+ * button. See {@link #setHomeButtonEnabled(boolean)}.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public static final int DISPLAY_HOME_AS_UP = 0x4;
+
+ /**
+ * Show the activity title and subtitle, if present.
+ *
+ * @see #setTitle(CharSequence)
+ * @see #setTitle(int)
+ * @see #setSubtitle(CharSequence)
+ * @see #setSubtitle(int)
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public static final int DISPLAY_SHOW_TITLE = 0x8;
+
+ /**
+ * Show the custom view if one has been set.
+ * @see #setCustomView(View)
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public static final int DISPLAY_SHOW_CUSTOM = 0x10;
+
+ /**
+ * Allow the title to wrap onto multiple lines if space is available
+ * @hide pending API approval
+ */
+ @UnsupportedAppUsage
+ public static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;
+
+ /**
+ * Set the action bar into custom navigation mode, supplying a view
+ * for custom navigation.
+ *
+ * Custom navigation views appear between the application icon and
+ * any action buttons and may use any space available there. Common
+ * use cases for custom navigation views might include an auto-suggesting
+ * address bar for a browser or other navigation mechanisms that do not
+ * translate well to provided navigation modes.
+ *
+ * @param view Custom navigation view to place in the ActionBar.
+ */
+ public abstract void setCustomView(View view);
+
+ /**
+ * Set the action bar into custom navigation mode, supplying a view
+ * for custom navigation.
+ *
+ * <p>Custom navigation views appear between the application icon and
+ * any action buttons and may use any space available there. Common
+ * use cases for custom navigation views might include an auto-suggesting
+ * address bar for a browser or other navigation mechanisms that do not
+ * translate well to provided navigation modes.</p>
+ *
+ * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for
+ * the custom view to be displayed.</p>
+ *
+ * @param view Custom navigation view to place in the ActionBar.
+ * @param layoutParams How this custom view should layout in the bar.
+ *
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setCustomView(View view, LayoutParams layoutParams);
+
+ /**
+ * Set the action bar into custom navigation mode, supplying a view
+ * for custom navigation.
+ *
+ * <p>Custom navigation views appear between the application icon and
+ * any action buttons and may use any space available there. Common
+ * use cases for custom navigation views might include an auto-suggesting
+ * address bar for a browser or other navigation mechanisms that do not
+ * translate well to provided navigation modes.</p>
+ *
+ * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for
+ * the custom view to be displayed.</p>
+ *
+ * @param resId Resource ID of a layout to inflate into the ActionBar.
+ *
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setCustomView(@LayoutRes int resId);
+
+ /**
+ * Set the icon to display in the 'home' section of the action bar.
+ * The action bar will use an icon specified by its style or the
+ * activity icon by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param resId Resource ID of a drawable to show as an icon.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setIcon(@DrawableRes int resId);
+
+ /**
+ * Set the icon to display in the 'home' section of the action bar.
+ * The action bar will use an icon specified by its style or the
+ * activity icon by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param icon Drawable to show as an icon.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setIcon(Drawable icon);
+
+ /**
+ * Set the logo to display in the 'home' section of the action bar.
+ * The action bar will use a logo specified by its style or the
+ * activity logo by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param resId Resource ID of a drawable to show as a logo.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setLogo(@DrawableRes int resId);
+
+ /**
+ * Set the logo to display in the 'home' section of the action bar.
+ * The action bar will use a logo specified by its style or the
+ * activity logo by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param logo Drawable to show as a logo.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setLogo(Drawable logo);
+
+ /**
+ * Set the adapter and navigation callback for list navigation mode.
+ *
+ * The supplied adapter will provide views for the expanded list as well as
+ * the currently selected item. (These may be displayed differently.)
+ *
+ * The supplied OnNavigationListener will alert the application when the user
+ * changes the current list selection.
+ *
+ * @param adapter An adapter that will provide views both to display
+ * the current navigation selection and populate views
+ * within the dropdown navigation menu.
+ * @param callback An OnNavigationListener that will receive events when the user
+ * selects a navigation item.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void setListNavigationCallbacks(SpinnerAdapter adapter,
+ OnNavigationListener callback);
+
+ /**
+ * Set the selected navigation item in list or tabbed navigation modes.
+ *
+ * @param position Position of the item to select.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void setSelectedNavigationItem(int position);
+
+ /**
+ * Get the position of the selected navigation item in list or tabbed navigation modes.
+ *
+ * @return Position of the selected item.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract int getSelectedNavigationIndex();
+
+ /**
+ * Get the number of navigation items present in the current navigation mode.
+ *
+ * @return Number of navigation items.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract int getNavigationItemCount();
+
+ /**
+ * Set the action bar's title. This will only be displayed if
+ * {@link #DISPLAY_SHOW_TITLE} is set.
+ *
+ * @param title Title to set
+ *
+ * @see #setTitle(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setTitle(CharSequence title);
+
+ /**
+ * Set the action bar's title. This will only be displayed if
+ * {@link #DISPLAY_SHOW_TITLE} is set.
+ *
+ * @param resId Resource ID of title string to set
+ *
+ * @see #setTitle(CharSequence)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setTitle(@StringRes int resId);
+
+ /**
+ * Set the action bar's subtitle. This will only be displayed if
+ * {@link #DISPLAY_SHOW_TITLE} is set. Set to null to disable the
+ * subtitle entirely.
+ *
+ * @param subtitle Subtitle to set
+ *
+ * @see #setSubtitle(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setSubtitle(CharSequence subtitle);
+
+ /**
+ * Set the action bar's subtitle. This will only be displayed if
+ * {@link #DISPLAY_SHOW_TITLE} is set.
+ *
+ * @param resId Resource ID of subtitle string to set
+ *
+ * @see #setSubtitle(CharSequence)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setSubtitle(@StringRes int resId);
+
+ /**
+ * Set display options. This changes all display option bits at once. To change
+ * a limited subset of display options, see {@link #setDisplayOptions(int, int)}.
+ *
+ * @param options A combination of the bits defined by the DISPLAY_ constants
+ * defined in ActionBar.
+ */
+ public abstract void setDisplayOptions(@DisplayOptions int options);
+
+ /**
+ * Set selected display options. Only the options specified by mask will be changed.
+ * To change all display option bits at once, see {@link #setDisplayOptions(int)}.
+ *
+ * <p>Example: setDisplayOptions(0, DISPLAY_SHOW_HOME) will disable the
+ * {@link #DISPLAY_SHOW_HOME} option.
+ * setDisplayOptions(DISPLAY_SHOW_HOME, DISPLAY_SHOW_HOME | DISPLAY_USE_LOGO)
+ * will enable {@link #DISPLAY_SHOW_HOME} and disable {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param options A combination of the bits defined by the DISPLAY_ constants
+ * defined in ActionBar.
+ * @param mask A bit mask declaring which display options should be changed.
+ */
+ public abstract void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask);
+
+ /**
+ * Set whether to display the activity logo rather than the activity icon.
+ * A logo is often a wider, more detailed image.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param useLogo true to use the activity logo, false to use the activity icon.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setDisplayUseLogoEnabled(boolean useLogo);
+
+ /**
+ * Set whether to include the application home affordance in the action bar.
+ * Home is presented as either an activity icon or logo.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param showHome true to show home, false otherwise.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setDisplayShowHomeEnabled(boolean showHome);
+
+ /**
+ * Set whether home should be displayed as an "up" affordance.
+ * Set this to true if selecting "home" returns up by a single level in your UI
+ * rather than back to the top level or front page.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param showHomeAsUp true to show the user that selecting home will return one
+ * level up rather than to the top level of the app.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setDisplayHomeAsUpEnabled(boolean showHomeAsUp);
+
+ /**
+ * Set whether an activity title/subtitle should be displayed.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param showTitle true to display a title/subtitle if present.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setDisplayShowTitleEnabled(boolean showTitle);
+
+ /**
+ * Set whether a custom view should be displayed, if set.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param showCustom true if the currently set custom view should be displayed, false otherwise.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ */
+ public abstract void setDisplayShowCustomEnabled(boolean showCustom);
+
+ /**
+ * Set the ActionBar's background. This will be used for the primary
+ * action bar.
+ *
+ * @param d Background drawable
+ * @see #setStackedBackgroundDrawable(Drawable)
+ * @see #setSplitBackgroundDrawable(Drawable)
+ */
+ public abstract void setBackgroundDrawable(@Nullable Drawable d);
+
+ /**
+ * Set the ActionBar's stacked background. This will appear
+ * in the second row/stacked bar on some devices and configurations.
+ *
+ * @param d Background drawable for the stacked row
+ */
+ public void setStackedBackgroundDrawable(Drawable d) { }
+
+ /**
+ * Set the ActionBar's split background. This will appear in
+ * the split action bar containing menu-provided action buttons
+ * on some devices and configurations.
+ * <p>You can enable split action bar with {@link android.R.attr#uiOptions}
+ *
+ * @param d Background drawable for the split bar
+ */
+ public void setSplitBackgroundDrawable(Drawable d) { }
+
+ /**
+ * @return The current custom view.
+ */
+ public abstract View getCustomView();
+
+ /**
+ * Returns the current ActionBar title in standard mode.
+ * Returns null if {@link #getNavigationMode()} would not return
+ * {@link #NAVIGATION_MODE_STANDARD}.
+ *
+ * @return The current ActionBar title or null.
+ */
+ public abstract CharSequence getTitle();
+
+ /**
+ * Returns the current ActionBar subtitle in standard mode.
+ * Returns null if {@link #getNavigationMode()} would not return
+ * {@link #NAVIGATION_MODE_STANDARD}.
+ *
+ * @return The current ActionBar subtitle or null.
+ */
+ public abstract CharSequence getSubtitle();
+
+ /**
+ * Returns the current navigation mode. The result will be one of:
+ * <ul>
+ * <li>{@link #NAVIGATION_MODE_STANDARD}</li>
+ * <li>{@link #NAVIGATION_MODE_LIST}</li>
+ * <li>{@link #NAVIGATION_MODE_TABS}</li>
+ * </ul>
+ *
+ * @return The current navigation mode.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ @NavigationMode
+ public abstract int getNavigationMode();
+
+ /**
+ * Set the current navigation mode.
+ *
+ * @param mode The new mode to set.
+ * @see #NAVIGATION_MODE_STANDARD
+ * @see #NAVIGATION_MODE_LIST
+ * @see #NAVIGATION_MODE_TABS
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void setNavigationMode(@NavigationMode int mode);
+
+ /**
+ * @return The current set of display options.
+ */
+ public abstract int getDisplayOptions();
+
+ /**
+ * Create and return a new {@link Tab}.
+ * This tab will not be included in the action bar until it is added.
+ *
+ * <p>Very often tabs will be used to switch between {@link Fragment}
+ * objects. Here is a typical implementation of such tabs:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java
+ * complete}
+ *
+ * @return A new Tab
+ *
+ * @see #addTab(Tab)
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract Tab newTab();
+
+ /**
+ * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+ * If this is the first tab to be added it will become the selected tab.
+ *
+ * @param tab Tab to add
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void addTab(Tab tab);
+
+ /**
+ * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+ *
+ * @param tab Tab to add
+ * @param setSelected True if the added tab should become the selected tab.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void addTab(Tab tab, boolean setSelected);
+
+ /**
+ * Add a tab for use in tabbed navigation mode. The tab will be inserted at
+ * <code>position</code>. If this is the first tab to be added it will become
+ * the selected tab.
+ *
+ * @param tab The tab to add
+ * @param position The new position of the tab
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void addTab(Tab tab, int position);
+
+ /**
+ * Add a tab for use in tabbed navigation mode. The tab will be insterted at
+ * <code>position</code>.
+ *
+ * @param tab The tab to add
+ * @param position The new position of the tab
+ * @param setSelected True if the added tab should become the selected tab.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void addTab(Tab tab, int position, boolean setSelected);
+
+ /**
+ * Remove a tab from the action bar. If the removed tab was selected it will be deselected
+ * and another tab will be selected if present.
+ *
+ * @param tab The tab to remove
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void removeTab(Tab tab);
+
+ /**
+ * Remove a tab from the action bar. If the removed tab was selected it will be deselected
+ * and another tab will be selected if present.
+ *
+ * @param position Position of the tab to remove
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void removeTabAt(int position);
+
+ /**
+ * Remove all tabs from the action bar and deselect the current tab.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void removeAllTabs();
+
+ /**
+ * Select the specified tab. If it is not a child of this action bar it will be added.
+ *
+ * <p>Note: If you want to select by index, use {@link #setSelectedNavigationItem(int)}.</p>
+ *
+ * @param tab Tab to select
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract void selectTab(Tab tab);
+
+ /**
+ * Returns the currently selected tab if in tabbed navigation mode and there is at least
+ * one tab present.
+ *
+ * @return The currently selected tab or null
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract Tab getSelectedTab();
+
+ /**
+ * Returns the tab at the specified index.
+ *
+ * @param index Index value in the range 0-get
+ * @return
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract Tab getTabAt(int index);
+
+ /**
+ * Returns the number of tabs currently registered with the action bar.
+ * @return Tab count
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public abstract int getTabCount();
+
+ /**
+ * Retrieve the current height of the ActionBar.
+ *
+ * @return The ActionBar's height
+ */
+ public abstract int getHeight();
+
+ /**
+ * Show the ActionBar if it is not currently showing.
+ * If the window hosting the ActionBar does not have the feature
+ * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+ * content to fit the new space available.
+ *
+ * <p>If you are hiding the ActionBar through
+ * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN},
+ * you should not call this function directly.
+ */
+ public abstract void show();
+
+ /**
+ * Hide the ActionBar if it is currently showing.
+ * If the window hosting the ActionBar does not have the feature
+ * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+ * content to fit the new space available.
+ *
+ * <p>Instead of calling this function directly, you can also cause an
+ * ActionBar using the overlay feature to hide through
+ * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}.
+ * Hiding the ActionBar through this system UI flag allows you to more
+ * seamlessly hide it in conjunction with other screen decorations.
+ */
+ public abstract void hide();
+
+ /**
+ * @return <code>true</code> if the ActionBar is showing, <code>false</code> otherwise.
+ */
+ public abstract boolean isShowing();
+
+ /**
+ * Add a listener that will respond to menu visibility change events.
+ *
+ * @param listener The new listener to add
+ */
+ public abstract void addOnMenuVisibilityListener(OnMenuVisibilityListener listener);
+
+ /**
+ * Remove a menu visibility listener. This listener will no longer receive menu
+ * visibility change events.
+ *
+ * @param listener A listener to remove that was previously added
+ */
+ public abstract void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener);
+
+ /**
+ * Enable or disable the "home" button in the corner of the action bar. (Note that this
+ * is the application home/up affordance on the action bar, not the systemwide home
+ * button.)
+ *
+ * <p>This defaults to true for packages targeting < API 14. For packages targeting
+ * API 14 or greater, the application should call this method to enable interaction
+ * with the home/up affordance.
+ *
+ * <p>Setting the {@link #DISPLAY_HOME_AS_UP} display option will automatically enable
+ * the home button.
+ *
+ * @param enabled true to enable the home button, false to disable the home button.
+ */
+ public void setHomeButtonEnabled(boolean enabled) { }
+
+ /**
+ * Returns a {@link Context} with an appropriate theme for creating views that
+ * will appear in the action bar. If you are inflating or instantiating custom views
+ * that will appear in an action bar, you should use the Context returned by this method.
+ * (This includes adapters used for list navigation mode.)
+ * This will ensure that views contrast properly against the action bar.
+ *
+ * @return A themed Context for creating views
+ */
+ public Context getThemedContext() { return null; }
+
+ /**
+ * Returns true if the Title field has been truncated during layout for lack
+ * of available space.
+ *
+ * @return true if the Title field has been truncated
+ * @hide pending API approval
+ */
+ public boolean isTitleTruncated() { return false; }
+
+ /**
+ * Set an alternate drawable to display next to the icon/logo/title
+ * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
+ * this mode to display an alternate selection for up navigation, such as a sliding drawer.
+ *
+ * <p>If you pass <code>null</code> to this method, the default drawable from the theme
+ * will be used.</p>
+ *
+ * <p>If you implement alternate or intermediate behavior around Up, you should also
+ * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
+ * to provide a correct description of the action for accessibility support.</p>
+ *
+ * @param indicator A drawable to use for the up indicator, or null to use the theme's default
+ *
+ * @see #setDisplayOptions(int, int)
+ * @see #setDisplayHomeAsUpEnabled(boolean)
+ * @see #setHomeActionContentDescription(int)
+ */
+ public void setHomeAsUpIndicator(Drawable indicator) { }
+
+ /**
+ * Set an alternate drawable to display next to the icon/logo/title
+ * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
+ * this mode to display an alternate selection for up navigation, such as a sliding drawer.
+ *
+ * <p>If you pass <code>0</code> to this method, the default drawable from the theme
+ * will be used.</p>
+ *
+ * <p>If you implement alternate or intermediate behavior around Up, you should also
+ * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
+ * to provide a correct description of the action for accessibility support.</p>
+ *
+ * @param resId Resource ID of a drawable to use for the up indicator, or null
+ * to use the theme's default
+ *
+ * @see #setDisplayOptions(int, int)
+ * @see #setDisplayHomeAsUpEnabled(boolean)
+ * @see #setHomeActionContentDescription(int)
+ */
+ public void setHomeAsUpIndicator(@DrawableRes int resId) { }
+
+ /**
+ * Set an alternate description for the Home/Up action, when enabled.
+ *
+ * <p>This description is commonly used for accessibility/screen readers when
+ * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.)
+ * Examples of this are, "Navigate Home" or "Navigate Up" depending on the
+ * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up
+ * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific
+ * functionality such as a sliding drawer, you should also set this to accurately
+ * describe the action.</p>
+ *
+ * <p>Setting this to <code>null</code> will use the system default description.</p>
+ *
+ * @param description New description for the Home action when enabled
+ * @see #setHomeAsUpIndicator(int)
+ * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
+ */
+ public void setHomeActionContentDescription(CharSequence description) { }
+
+ /**
+ * Set an alternate description for the Home/Up action, when enabled.
+ *
+ * <p>This description is commonly used for accessibility/screen readers when
+ * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.)
+ * Examples of this are, "Navigate Home" or "Navigate Up" depending on the
+ * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up
+ * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific
+ * functionality such as a sliding drawer, you should also set this to accurately
+ * describe the action.</p>
+ *
+ * <p>Setting this to <code>0</code> will use the system default description.</p>
+ *
+ * @param resId Resource ID of a string to use as the new description
+ * for the Home action when enabled
+ * @see #setHomeAsUpIndicator(int)
+ * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
+ */
+ public void setHomeActionContentDescription(@StringRes int resId) { }
+
+ /**
+ * Enable hiding the action bar on content scroll.
+ *
+ * <p>If enabled, the action bar will scroll out of sight along with a
+ * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content.
+ * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode}
+ * to enable hiding on content scroll.</p>
+ *
+ * <p>When partially scrolled off screen the action bar is considered
+ * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view.
+ * </p>
+ * @param hideOnContentScroll true to enable hiding on content scroll.
+ */
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll) {
+ throw new UnsupportedOperationException("Hide on content scroll is not supported in " +
+ "this action bar configuration.");
+ }
+ }
+
+ /**
+ * Return whether the action bar is configured to scroll out of sight along with
+ * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}.
+ *
+ * @return true if hide-on-content-scroll is enabled
+ * @see #setHideOnContentScrollEnabled(boolean)
+ */
+ public boolean isHideOnContentScrollEnabled() {
+ return false;
+ }
+
+ /**
+ * Return the current vertical offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @return The action bar's offset toward its fully hidden state in pixels
+ */
+ public int getHideOffset() {
+ return 0;
+ }
+
+ /**
+ * Set the current hide offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @param offset The action bar's offset toward its fully hidden state in pixels.
+ */
+ public void setHideOffset(int offset) {
+ if (offset != 0) {
+ throw new UnsupportedOperationException("Setting an explicit action bar hide offset " +
+ "is not supported in this action bar configuration.");
+ }
+ }
+
+ /**
+ * Set the Z-axis elevation of the action bar in pixels.
+ *
+ * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+ * values are closer to the user.</p>
+ *
+ * @param elevation Elevation value in pixels
+ */
+ public void setElevation(float elevation) {
+ if (elevation != 0) {
+ throw new UnsupportedOperationException("Setting a non-zero elevation is " +
+ "not supported in this action bar configuration.");
+ }
+ }
+
+ /**
+ * Get the Z-axis elevation of the action bar in pixels.
+ *
+ * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+ * values are closer to the user.</p>
+ *
+ * @return Elevation value in pixels
+ */
+ public float getElevation() {
+ return 0;
+ }
+
+ /** @hide */
+ public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setShowHideAnimationEnabled(boolean enabled) {
+ }
+
+ /** @hide */
+ public void onConfigurationChanged(Configuration config) {
+ }
+
+ /** @hide */
+ public void dispatchMenuVisibilityChanged(boolean visible) {
+ }
+
+ /** @hide */
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return null;
+ }
+
+ /** @hide */
+ public boolean openOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean closeOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean invalidateOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean onMenuKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /** @hide */
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ /** @hide */
+ public void setWindowTitle(CharSequence title) {
+ }
+
+ /** @hide */
+ public void onDestroy() {
+ }
+
+ /**
+ * Listener interface for ActionBar navigation events.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public interface OnNavigationListener {
+ /**
+ * This method is called whenever a navigation item in your action bar
+ * is selected.
+ *
+ * @param itemPosition Position of the item clicked.
+ * @param itemId ID of the item clicked.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onNavigationItemSelected(int itemPosition, long itemId);
+ }
+
+ /**
+ * Listener for receiving events when action bar menus are shown or hidden.
+ */
+ public interface OnMenuVisibilityListener {
+ /**
+ * Called when an action bar menu is shown or hidden. Applications may want to use
+ * this to tune auto-hiding behavior for the action bar or pause/resume video playback,
+ * gameplay, or other activity within the main content area.
+ *
+ * @param isVisible True if an action bar menu is now visible, false if no action bar
+ * menus are visible.
+ */
+ public void onMenuVisibilityChanged(boolean isVisible);
+ }
+
+ /**
+ * A tab in the action bar.
+ *
+ * <p>Tabs manage the hiding and showing of {@link Fragment}s.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public static abstract class Tab {
+ /**
+ * An invalid position for a tab.
+ *
+ * @see #getPosition()
+ */
+ public static final int INVALID_POSITION = -1;
+
+ /**
+ * Return the current position of this tab in the action bar.
+ *
+ * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
+ * the action bar.
+ */
+ public abstract int getPosition();
+
+ /**
+ * Return the icon associated with this tab.
+ *
+ * @return The tab's icon
+ */
+ public abstract Drawable getIcon();
+
+ /**
+ * Return the text of this tab.
+ *
+ * @return The tab's text
+ */
+ public abstract CharSequence getText();
+
+ /**
+ * Set the icon displayed on this tab.
+ *
+ * @param icon The drawable to use as an icon
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setIcon(Drawable icon);
+
+ /**
+ * Set the icon displayed on this tab.
+ *
+ * @param resId Resource ID referring to the drawable to use as an icon
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setIcon(@DrawableRes int resId);
+
+ /**
+ * Set the text displayed on this tab. Text may be truncated if there is not
+ * room to display the entire string.
+ *
+ * @param text The text to display
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setText(CharSequence text);
+
+ /**
+ * Set the text displayed on this tab. Text may be truncated if there is not
+ * room to display the entire string.
+ *
+ * @param resId A resource ID referring to the text that should be displayed
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setText(@StringRes int resId);
+
+ /**
+ * Set a custom view to be used for this tab. This overrides values set by
+ * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+ *
+ * @param view Custom view to be used as a tab.
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setCustomView(View view);
+
+ /**
+ * Set a custom view to be used for this tab. This overrides values set by
+ * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+ *
+ * @param layoutResId A layout resource to inflate and use as a custom tab view
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setCustomView(@LayoutRes int layoutResId);
+
+ /**
+ * Retrieve a previously set custom view for this tab.
+ *
+ * @return The custom view set by {@link #setCustomView(View)}.
+ */
+ public abstract View getCustomView();
+
+ /**
+ * Give this Tab an arbitrary object to hold for later use.
+ *
+ * @param obj Object to store
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setTag(Object obj);
+
+ /**
+ * @return This Tab's tag object.
+ */
+ public abstract Object getTag();
+
+ /**
+ * Set the {@link TabListener} that will handle switching to and from this tab.
+ * All tabs must have a TabListener set before being added to the ActionBar.
+ *
+ * @param listener Listener to handle tab selection events
+ * @return The current instance for call chaining
+ */
+ public abstract Tab setTabListener(TabListener listener);
+
+ /**
+ * Select this tab. Only valid if the tab has been added to the action bar.
+ */
+ public abstract void select();
+
+ /**
+ * Set a description of this tab's content for use in accessibility support.
+ * If no content description is provided the title will be used.
+ *
+ * @param resId A resource ID referring to the description text
+ * @return The current instance for call chaining
+ * @see #setContentDescription(CharSequence)
+ * @see #getContentDescription()
+ */
+ public abstract Tab setContentDescription(@StringRes int resId);
+
+ /**
+ * Set a description of this tab's content for use in accessibility support.
+ * If no content description is provided the title will be used.
+ *
+ * @param contentDesc Description of this tab's content
+ * @return The current instance for call chaining
+ * @see #setContentDescription(int)
+ * @see #getContentDescription()
+ */
+ public abstract Tab setContentDescription(CharSequence contentDesc);
+
+ /**
+ * Gets a brief description of this tab's content for use in accessibility support.
+ *
+ * @return Description of this tab's content
+ * @see #setContentDescription(CharSequence)
+ * @see #setContentDescription(int)
+ */
+ public abstract CharSequence getContentDescription();
+ }
+
+ /**
+ * Callback interface invoked when a tab is focused, unfocused, added, or removed.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
+ */
+ @Deprecated
+ public interface TabListener {
+ /**
+ * Called when a tab enters the selected state.
+ *
+ * @param tab The tab that was selected
+ * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+ * during a tab switch. The previous tab's unselect and this tab's select will be
+ * executed in a single transaction. This FragmentTransaction does not support
+ * being added to the back stack.
+ */
+ public void onTabSelected(Tab tab, FragmentTransaction ft);
+
+ /**
+ * Called when a tab exits the selected state.
+ *
+ * @param tab The tab that was unselected
+ * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+ * during a tab switch. This tab's unselect and the newly selected tab's select
+ * will be executed in a single transaction. This FragmentTransaction does not
+ * support being added to the back stack.
+ */
+ public void onTabUnselected(Tab tab, FragmentTransaction ft);
+
+ /**
+ * Called when a tab that is already selected is chosen again by the user.
+ * Some applications may use this action to return to the top level of a category.
+ *
+ * @param tab The tab that was reselected.
+ * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+ * once this method returns. This FragmentTransaction does not support
+ * being added to the back stack.
+ */
+ public void onTabReselected(Tab tab, FragmentTransaction ft);
+ }
+
+ /**
+ * Per-child layout information associated with action bar custom views.
+ *
+ * @attr ref android.R.styleable#ActionBar_LayoutParams_layout_gravity
+ */
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ /**
+ * Gravity for the view associated with these LayoutParams.
+ *
+ * @see android.view.Gravity
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = -1, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
+ @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
+ @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
+ @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.START, to = "START"),
+ @ViewDebug.IntToString(from = Gravity.END, to = "END"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
+ })
+ @InspectableProperty(
+ name = "layout_gravity",
+ valueType = InspectableProperty.ValueType.GRAVITY)
+ public int gravity = Gravity.NO_GRAVITY;
+
+ public LayoutParams(@NonNull Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ TypedArray a = c.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ActionBar_LayoutParams);
+ gravity = a.getInt(
+ com.android.internal.R.styleable.ActionBar_LayoutParams_layout_gravity,
+ Gravity.NO_GRAVITY);
+ a.recycle();
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+ }
+
+ public LayoutParams(int width, int height, int gravity) {
+ super(width, height);
+
+ this.gravity = gravity;
+ }
+
+ public LayoutParams(int gravity) {
+ this(WRAP_CONTENT, MATCH_PARENT, gravity);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ this.gravity = source.gravity;
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ /*
+ * Note for framework developers:
+ *
+ * You might notice that ActionBar.LayoutParams is missing a constructor overload
+ * for MarginLayoutParams. While it may seem like a good idea to add one, at this
+ * point it's dangerous for source compatibility. Upon building against a new
+ * version of the SDK an app can end up statically linking to the new MarginLayoutParams
+ * overload, causing a crash when running on older platform versions with no other changes.
+ */
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("gravity", gravity);
+ }
+ }
+}
diff --git a/android-34/android/app/Activity.java b/android-34/android/app/Activity.java
new file mode 100644
index 0000000..8021ce0
--- /dev/null
+++ b/android-34/android/app/Activity.java
@@ -0,0 +1,9430 @@
+/*
+ * 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.app;
+
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.inMultiWindowMode;
+import static android.os.Process.myUid;
+
+import static java.lang.Character.MIN_VALUE;
+
+import android.annotation.AnimRes;
+import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.LayoutRes;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.app.VoiceInteractor.Request;
+import android.app.admin.DevicePolicyManager;
+import android.app.assist.AssistContent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.LocusId;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.BadParcelableException;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.GraphicsEnvironment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.service.voice.VoiceInteractionSession;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.TextKeyListener;
+import android.transition.Scene;
+import android.transition.TransitionManager;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Dumpable;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Pair;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SuperNotCalledException;
+import android.view.ActionMode;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ContextThemeWrapper;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationDefinition;
+import android.view.SearchEvent;
+import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewManager;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.ActivityConfigCallback;
+import android.view.Window;
+import android.view.Window.WindowControllerCallback;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.autofill.AutofillClientController;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager.AutofillClient;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
+import android.view.translation.TranslationSpec;
+import android.view.translation.UiTranslationController;
+import android.view.translation.UiTranslationSpec;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.Toolbar;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.SplashScreen;
+import android.window.WindowOnBackInvokedDispatcher;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.ToolbarActionBar;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.PhoneWindow;
+import com.android.internal.util.dump.DumpableContainerImpl;
+
+import dalvik.system.VMRuntime;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+
+/**
+ * An activity is a single, focused thing that the user can do. Almost all
+ * activities interact with the user, so the Activity class takes care of
+ * creating a window for you in which you can place your UI with
+ * {@link #setContentView}. While activities are often presented to the user
+ * as full-screen windows, they can also be used in other ways: as floating
+ * windows (via a theme with {@link android.R.attr#windowIsFloating} set),
+ * <a href="https://developer.android.com/guide/topics/ui/multi-window">
+ * Multi-Window mode</a> or embedded into other windows.
+ *
+ * There are two methods almost all subclasses of Activity will implement:
+ *
+ * <ul>
+ * <li> {@link #onCreate} is where you initialize your activity. Most
+ * importantly, here you will usually call {@link #setContentView(int)}
+ * with a layout resource defining your UI, and using {@link #findViewById}
+ * to retrieve the widgets in that UI that you need to interact with
+ * programmatically.
+ *
+ * <li> {@link #onPause} is where you deal with the user pausing active
+ * interaction with the activity. Any changes made by the user should at
+ * this point be committed (usually to the
+ * {@link android.content.ContentProvider} holding the data). In this
+ * state the activity is still visible on screen.
+ * </ul>
+ *
+ * <p>To be of use with {@link android.content.Context#startActivity Context.startActivity()}, all
+ * activity classes must have a corresponding
+ * {@link android.R.styleable#AndroidManifestActivity <activity>}
+ * declaration in their package's <code>AndroidManifest.xml</code>.</p>
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#Fragments">Fragments</a>
+ * <li><a href="#ActivityLifecycle">Activity Lifecycle</a>
+ * <li><a href="#ConfigurationChanges">Configuration Changes</a>
+ * <li><a href="#StartingActivities">Starting Activities and Getting Results</a>
+ * <li><a href="#SavingPersistentState">Saving Persistent State</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * </ol>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>The Activity class is an important part of an application's overall lifecycle,
+ * and the way activities are launched and put together is a fundamental
+ * part of the platform's application model. For a detailed perspective on the structure of an
+ * Android application and how activities behave, please read the
+ * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a> and
+ * <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back Stack</a>
+ * developer guides.</p>
+ *
+ * <p>You can also find a detailed discussion about how to create activities in the
+ * <a href="{@docRoot}guide/components/activities.html">Activities</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <a name="Fragments"></a>
+ * <h3>Fragments</h3>
+ *
+ * <p>The {@link androidx.fragment.app.FragmentActivity} subclass
+ * can make use of the {@link androidx.fragment.app.Fragment} class to better
+ * modularize their code, build more sophisticated user interfaces for larger
+ * screens, and help scale their application between small and large screens.</p>
+ *
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
+ *
+ * <a name="ActivityLifecycle"></a>
+ * <h3>Activity Lifecycle</h3>
+ *
+ * <p>Activities in the system are managed as
+ * <a href="https://developer.android.com/guide/components/activities/tasks-and-back-stack">
+ * activity stacks</a>. When a new activity is started, it is usually placed on the top of the
+ * current stack and becomes the running activity -- the previous activity always remains
+ * below it in the stack, and will not come to the foreground again until
+ * the new activity exits. There can be one or multiple activity stacks visible
+ * on screen.</p>
+ *
+ * <p>An activity has essentially four states:</p>
+ * <ul>
+ * <li>If an activity is in the foreground of the screen (at the highest position of the topmost
+ * stack), it is <em>active</em> or <em>running</em>. This is usually the activity that the
+ * user is currently interacting with.</li>
+ * <li>If an activity has lost focus but is still presented to the user, it is <em>visible</em>.
+ * It is possible if a new non-full-sized or transparent activity has focus on top of your
+ * activity, another activity has higher position in multi-window mode, or the activity
+ * itself is not focusable in current windowing mode. Such activity is completely alive (it
+ * maintains all state and member information and remains attached to the window manager).
+ * <li>If an activity is completely obscured by another activity,
+ * it is <em>stopped</em> or <em>hidden</em>. It still retains all state and member
+ * information, however, it is no longer visible to the user so its window is hidden
+ * and it will often be killed by the system when memory is needed elsewhere.</li>
+ * <li>The system can drop the activity from memory by either asking it to finish,
+ * or simply killing its process, making it <em>destroyed</em>. When it is displayed again
+ * to the user, it must be completely restarted and restored to its previous state.</li>
+ * </ul>
+ *
+ * <p>The following diagram shows the important state paths of an Activity.
+ * The square rectangles represent callback methods you can implement to
+ * perform operations when the Activity moves between states. The colored
+ * ovals are major states the Activity can be in.</p>
+ *
+ * <p><img src="../../../images/activity_lifecycle.png"
+ * alt="State diagram for an Android Activity Lifecycle." border="0" /></p>
+ *
+ * <p>There are three key loops you may be interested in monitoring within your
+ * activity:
+ *
+ * <ul>
+ * <li>The <b>entire lifetime</b> of an activity happens between the first call
+ * to {@link android.app.Activity#onCreate} through to a single final call
+ * to {@link android.app.Activity#onDestroy}. An activity will do all setup
+ * of "global" state in onCreate(), and release all remaining resources in
+ * onDestroy(). For example, if it has a thread running in the background
+ * to download data from the network, it may create that thread in onCreate()
+ * and then stop the thread in onDestroy().
+ *
+ * <li>The <b>visible lifetime</b> of an activity happens between a call to
+ * {@link android.app.Activity#onStart} until a corresponding call to
+ * {@link android.app.Activity#onStop}. During this time the user can see the
+ * activity on-screen, though it may not be in the foreground and interacting
+ * with the user. Between these two methods you can maintain resources that
+ * are needed to show the activity to the user. For example, you can register
+ * a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes
+ * that impact your UI, and unregister it in onStop() when the user no
+ * longer sees what you are displaying. The onStart() and onStop() methods
+ * can be called multiple times, as the activity becomes visible and hidden
+ * to the user.
+ *
+ * <li>The <b>foreground lifetime</b> of an activity happens between a call to
+ * {@link android.app.Activity#onResume} until a corresponding call to
+ * {@link android.app.Activity#onPause}. During this time the activity is
+ * visible, active and interacting with the user. An activity
+ * can frequently go between the resumed and paused states -- for example when
+ * the device goes to sleep, when an activity result is delivered, when a new
+ * intent is delivered -- so the code in these methods should be fairly
+ * lightweight.
+ * </ul>
+ *
+ * <p>The entire lifecycle of an activity is defined by the following
+ * Activity methods. All of these are hooks that you can override
+ * to do appropriate work when the activity changes state. All
+ * activities will implement {@link android.app.Activity#onCreate}
+ * to do their initial setup; many will also implement
+ * {@link android.app.Activity#onPause} to commit changes to data and
+ * prepare to pause interacting with the user, and {@link android.app.Activity#onStop}
+ * to handle no longer being visible on screen. You should always
+ * call up to your superclass when implementing these methods.</p>
+ *
+ * </p>
+ * <pre class="prettyprint">
+ * public class Activity extends ApplicationContext {
+ * protected void onCreate(Bundle savedInstanceState);
+ *
+ * protected void onStart();
+ *
+ * protected void onRestart();
+ *
+ * protected void onResume();
+ *
+ * protected void onPause();
+ *
+ * protected void onStop();
+ *
+ * protected void onDestroy();
+ * }
+ * </pre>
+ *
+ * <p>In general the movement through an activity's lifecycle looks like
+ * this:</p>
+ *
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * <colgroup align="left" span="3" />
+ * <colgroup align="left" />
+ * <colgroup align="center" />
+ * <colgroup align="center" />
+ *
+ * <thead>
+ * <tr><th colspan="3">Method</th> <th>Description</th> <th>Killable?</th> <th>Next</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr><td colspan="3" align="left" border="0">{@link android.app.Activity#onCreate onCreate()}</td>
+ * <td>Called when the activity is first created.
+ * This is where you should do all of your normal static set up:
+ * create views, bind data to lists, etc. This method also
+ * provides you with a Bundle containing the activity's previously
+ * frozen state, if there was one.
+ * <p>Always followed by <code>onStart()</code>.</td>
+ * <td align="center">No</td>
+ * <td align="center"><code>onStart()</code></td>
+ * </tr>
+ *
+ * <tr><td rowspan="5" style="border-left: none; border-right: none;"> </td>
+ * <td colspan="2" align="left" border="0">{@link android.app.Activity#onRestart onRestart()}</td>
+ * <td>Called after your activity has been stopped, prior to it being
+ * started again.
+ * <p>Always followed by <code>onStart()</code></td>
+ * <td align="center">No</td>
+ * <td align="center"><code>onStart()</code></td>
+ * </tr>
+ *
+ * <tr><td colspan="2" align="left" border="0">{@link android.app.Activity#onStart onStart()}</td>
+ * <td>Called when the activity is becoming visible to the user.
+ * <p>Followed by <code>onResume()</code> if the activity comes
+ * to the foreground, or <code>onStop()</code> if it becomes hidden.</td>
+ * <td align="center">No</td>
+ * <td align="center"><code>onResume()</code> or <code>onStop()</code></td>
+ * </tr>
+ *
+ * <tr><td rowspan="2" style="border-left: none;"> </td>
+ * <td align="left" border="0">{@link android.app.Activity#onResume onResume()}</td>
+ * <td>Called when the activity will start
+ * interacting with the user. At this point your activity is at
+ * the top of its activity stack, with user input going to it.
+ * <p>Always followed by <code>onPause()</code>.</td>
+ * <td align="center">No</td>
+ * <td align="center"><code>onPause()</code></td>
+ * </tr>
+ *
+ * <tr><td align="left" border="0">{@link android.app.Activity#onPause onPause()}</td>
+ * <td>Called when the activity loses foreground state, is no longer focusable or before
+ * transition to stopped/hidden or destroyed state. The activity is still visible to
+ * user, so it's recommended to keep it visually active and continue updating the UI.
+ * Implementations of this method must be very quick because
+ * the next activity will not be resumed until this method returns.
+ * <p>Followed by either <code>onResume()</code> if the activity
+ * returns back to the front, or <code>onStop()</code> if it becomes
+ * invisible to the user.</td>
+ * <td align="center"><font color="#800000"><strong>Pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB}</strong></font></td>
+ * <td align="center"><code>onResume()</code> or<br>
+ * <code>onStop()</code></td>
+ * </tr>
+ *
+ * <tr><td colspan="2" align="left" border="0">{@link android.app.Activity#onStop onStop()}</td>
+ * <td>Called when the activity is no longer visible to the user. This may happen either
+ * because a new activity is being started on top, an existing one is being brought in
+ * front of this one, or this one is being destroyed. This is typically used to stop
+ * animations and refreshing the UI, etc.
+ * <p>Followed by either <code>onRestart()</code> if
+ * this activity is coming back to interact with the user, or
+ * <code>onDestroy()</code> if this activity is going away.</td>
+ * <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ * <td align="center"><code>onRestart()</code> or<br>
+ * <code>onDestroy()</code></td>
+ * </tr>
+ *
+ * <tr><td colspan="3" align="left" border="0">{@link android.app.Activity#onDestroy onDestroy()}</td>
+ * <td>The final call you receive before your
+ * activity is destroyed. This can happen either because the
+ * activity is finishing (someone called {@link Activity#finish} on
+ * it), or because the system is temporarily destroying this
+ * instance of the activity to save space. You can distinguish
+ * between these two scenarios with the {@link
+ * Activity#isFinishing} method.</td>
+ * <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ * <td align="center"><em>nothing</em></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <p>Note the "Killable" column in the above table -- for those methods that
+ * are marked as being killable, after that method returns the process hosting the
+ * activity may be killed by the system <em>at any time</em> without another line
+ * of its code being executed. Because of this, you should use the
+ * {@link #onPause} method to write any persistent data (such as user edits)
+ * to storage. In addition, the method
+ * {@link #onSaveInstanceState(Bundle)} is called before placing the activity
+ * in such a background state, allowing you to save away any dynamic instance
+ * state in your activity into the given Bundle, to be later received in
+ * {@link #onCreate} if the activity needs to be re-created.
+ * See the <a href="#ProcessLifecycle">Process Lifecycle</a>
+ * section for more information on how the lifecycle of a process is tied
+ * to the activities it is hosting. Note that it is important to save
+ * persistent data in {@link #onPause} instead of {@link #onSaveInstanceState}
+ * because the latter is not part of the lifecycle callbacks, so will not
+ * be called in every situation as described in its documentation.</p>
+ *
+ * <p class="note">Be aware that these semantics will change slightly between
+ * applications targeting platforms starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * vs. those targeting prior platforms. Starting with Honeycomb, an application
+ * is not in the killable state until its {@link #onStop} has returned. This
+ * impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be
+ * safely called after {@link #onPause()}) and allows an application to safely
+ * wait until {@link #onStop()} to save persistent state.</p>
+ *
+ * <p class="note">For applications targeting platforms starting with
+ * {@link android.os.Build.VERSION_CODES#P} {@link #onSaveInstanceState(Bundle)}
+ * will always be called after {@link #onStop}, so an application may safely
+ * perform fragment transactions in {@link #onStop} and will be able to save
+ * persistent state later.</p>
+ *
+ * <p>For those methods that are not marked as being killable, the activity's
+ * process will not be killed by the system starting from the time the method
+ * is called and continuing after it returns. Thus an activity is in the killable
+ * state, for example, between after <code>onStop()</code> to the start of
+ * <code>onResume()</code>. Keep in mind that under extreme memory pressure the
+ * system can kill the application process at any time.</p>
+ *
+ * <a name="ConfigurationChanges"></a>
+ * <h3>Configuration Changes</h3>
+ *
+ * <p>If the configuration of the device (as defined by the
+ * {@link Configuration Resources.Configuration} class) changes,
+ * then anything displaying a user interface will need to update to match that
+ * configuration. Because Activity is the primary mechanism for interacting
+ * with the user, it includes special support for handling configuration
+ * changes.</p>
+ *
+ * <p>Unless you specify otherwise, a configuration change (such as a change
+ * in screen orientation, language, input devices, etc) will cause your
+ * current activity to be <em>destroyed</em>, going through the normal activity
+ * lifecycle process of {@link #onPause},
+ * {@link #onStop}, and {@link #onDestroy} as appropriate. If the activity
+ * had been in the foreground or visible to the user, once {@link #onDestroy} is
+ * called in that instance then a new instance of the activity will be
+ * created, with whatever savedInstanceState the previous instance had generated
+ * from {@link #onSaveInstanceState}.</p>
+ *
+ * <p>This is done because any application resource,
+ * including layout files, can change based on any configuration value. Thus
+ * the only safe way to handle a configuration change is to re-retrieve all
+ * resources, including layouts, drawables, and strings. Because activities
+ * must already know how to save their state and re-create themselves from
+ * that state, this is a convenient way to have an activity restart itself
+ * with a new configuration.</p>
+ *
+ * <p>In some special cases, you may want to bypass restarting of your
+ * activity based on one or more types of configuration changes. This is
+ * done with the {@link android.R.attr#configChanges android:configChanges}
+ * attribute in its manifest. For any types of configuration changes you say
+ * that you handle there, you will receive a call to your current activity's
+ * {@link #onConfigurationChanged} method instead of being restarted. If
+ * a configuration change involves any that you do not handle, however, the
+ * activity will still be restarted and {@link #onConfigurationChanged}
+ * will not be called.</p>
+ *
+ * <a name="StartingActivities"></a>
+ * <h3>Starting Activities and Getting Results</h3>
+ *
+ * <p>The {@link android.app.Activity#startActivity}
+ * method is used to start a
+ * new activity, which will be placed at the top of the activity stack. It
+ * takes a single argument, an {@link android.content.Intent Intent},
+ * which describes the activity
+ * to be executed.</p>
+ *
+ * <p>Sometimes you want to get a result back from an activity when it
+ * ends. For example, you may start an activity that lets the user pick
+ * a person in a list of contacts; when it ends, it returns the person
+ * that was selected. To do this, you call the
+ * {@link android.app.Activity#startActivityForResult(Intent, int)}
+ * version with a second integer parameter identifying the call. The result
+ * will come back through your {@link android.app.Activity#onActivityResult}
+ * method.</p>
+ *
+ * <p>When an activity exits, it can call
+ * {@link android.app.Activity#setResult(int)}
+ * to return data back to its parent. It must always supply a result code,
+ * which can be the standard results RESULT_CANCELED, RESULT_OK, or any
+ * custom values starting at RESULT_FIRST_USER. In addition, it can optionally
+ * return back an Intent containing any additional data it wants. All of this
+ * information appears back on the
+ * parent's <code>Activity.onActivityResult()</code>, along with the integer
+ * identifier it originally supplied.</p>
+ *
+ * <p>If a child activity fails for any reason (such as crashing), the parent
+ * activity will receive a result with the code RESULT_CANCELED.</p>
+ *
+ * <pre class="prettyprint">
+ * public class MyActivity extends Activity {
+ * ...
+ *
+ * static final int PICK_CONTACT_REQUEST = 0;
+ *
+ * public boolean onKeyDown(int keyCode, KeyEvent event) {
+ * if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ * // When the user center presses, let them pick a contact.
+ * startActivityForResult(
+ * new Intent(Intent.ACTION_PICK,
+ * new Uri("content://contacts")),
+ * PICK_CONTACT_REQUEST);
+ * return true;
+ * }
+ * return false;
+ * }
+ *
+ * protected void onActivityResult(int requestCode, int resultCode,
+ * Intent data) {
+ * if (requestCode == PICK_CONTACT_REQUEST) {
+ * if (resultCode == RESULT_OK) {
+ * // A contact was picked. Here we will just display it
+ * // to the user.
+ * startActivity(new Intent(Intent.ACTION_VIEW, data));
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * <a name="SavingPersistentState"></a>
+ * <h3>Saving Persistent State</h3>
+ *
+ * <p>There are generally two kinds of persistent state that an activity
+ * will deal with: shared document-like data (typically stored in a SQLite
+ * database using a {@linkplain android.content.ContentProvider content provider})
+ * and internal state such as user preferences.</p>
+ *
+ * <p>For content provider data, we suggest that activities use an
+ * "edit in place" user model. That is, any edits a user makes are effectively
+ * made immediately without requiring an additional confirmation step.
+ * Supporting this model is generally a simple matter of following two rules:</p>
+ *
+ * <ul>
+ * <li> <p>When creating a new document, the backing database entry or file for
+ * it is created immediately. For example, if the user chooses to write
+ * a new email, a new entry for that email is created as soon as they
+ * start entering data, so that if they go to any other activity after
+ * that point this email will now appear in the list of drafts.</p>
+ * <li> <p>When an activity's <code>onPause()</code> method is called, it should
+ * commit to the backing content provider or file any changes the user
+ * has made. This ensures that those changes will be seen by any other
+ * activity that is about to run. You will probably want to commit
+ * your data even more aggressively at key times during your
+ * activity's lifecycle: for example before starting a new
+ * activity, before finishing your own activity, when the user
+ * switches between input fields, etc.</p>
+ * </ul>
+ *
+ * <p>This model is designed to prevent data loss when a user is navigating
+ * between activities, and allows the system to safely kill an activity (because
+ * system resources are needed somewhere else) at any time after it has been
+ * stopped (or paused on platform versions before {@link android.os.Build.VERSION_CODES#HONEYCOMB}).
+ * Note this implies that the user pressing BACK from your activity does <em>not</em>
+ * mean "cancel" -- it means to leave the activity with its current contents
+ * saved away. Canceling edits in an activity must be provided through
+ * some other mechanism, such as an explicit "revert" or "undo" option.</p>
+ *
+ * <p>See the {@linkplain android.content.ContentProvider content package} for
+ * more information about content providers. These are a key aspect of how
+ * different activities invoke and propagate data between themselves.</p>
+ *
+ * <p>The Activity class also provides an API for managing internal persistent state
+ * associated with an activity. This can be used, for example, to remember
+ * the user's preferred initial display in a calendar (day view or week view)
+ * or the user's default home page in a web browser.</p>
+ *
+ * <p>Activity persistent state is managed
+ * with the method {@link #getPreferences},
+ * allowing you to retrieve and
+ * modify a set of name/value pairs associated with the activity. To use
+ * preferences that are shared across multiple application components
+ * (activities, receivers, services, providers), you can use the underlying
+ * {@link Context#getSharedPreferences Context.getSharedPreferences()} method
+ * to retrieve a preferences
+ * object stored under a specific name.
+ * (Note that it is not possible to share settings data across application
+ * packages -- for that you will need a content provider.)</p>
+ *
+ * <p>Here is an excerpt from a calendar activity that stores the user's
+ * preferred view mode in its persistent settings:</p>
+ *
+ * <pre class="prettyprint">
+ * public class CalendarActivity extends Activity {
+ * ...
+ *
+ * static final int DAY_VIEW_MODE = 0;
+ * static final int WEEK_VIEW_MODE = 1;
+ *
+ * private SharedPreferences mPrefs;
+ * private int mCurViewMode;
+ *
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * mPrefs = getSharedPreferences(getLocalClassName(), MODE_PRIVATE);
+ * mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE);
+ * }
+ *
+ * protected void onPause() {
+ * super.onPause();
+ *
+ * SharedPreferences.Editor ed = mPrefs.edit();
+ * ed.putInt("view_mode", mCurViewMode);
+ * ed.commit();
+ * }
+ * }
+ * </pre>
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ *
+ * <p>The ability to start a particular Activity can be enforced when it is
+ * declared in its
+ * manifest's {@link android.R.styleable#AndroidManifestActivity <activity>}
+ * tag. By doing so, other applications will need to declare a corresponding
+ * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
+ * element in their own manifest to be able to start that activity.
+ *
+ * <p>When starting an Activity you can set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the
+ * Activity access to the specific URIs in the Intent. Access will remain
+ * until the Activity has finished (it will remain across the hosting
+ * process being killed and other temporary destruction). As of
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD}, if the Activity
+ * was already created and a new Intent is being delivered to
+ * {@link #onNewIntent(Intent)}, any newly granted URI permissions will be added
+ * to the existing ones it holds.
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+ * document for more information on permissions and security in general.
+ *
+ * <a name="ProcessLifecycle"></a>
+ * <h3>Process Lifecycle</h3>
+ *
+ * <p>The Android system attempts to keep an application process around for as
+ * long as possible, but eventually will need to remove old processes when
+ * memory runs low. As described in <a href="#ActivityLifecycle">Activity
+ * Lifecycle</a>, the decision about which process to remove is intimately
+ * tied to the state of the user's interaction with it. In general, there
+ * are four states a process can be in based on the activities running in it,
+ * listed here in order of importance. The system will kill less important
+ * processes (the last ones) before it resorts to killing more important
+ * processes (the first ones).
+ *
+ * <ol>
+ * <li> <p>The <b>foreground activity</b> (the activity at the top of the screen
+ * that the user is currently interacting with) is considered the most important.
+ * Its process will only be killed as a last resort, if it uses more memory
+ * than is available on the device. Generally at this point the device has
+ * reached a memory paging state, so this is required in order to keep the user
+ * interface responsive.
+ * <li> <p>A <b>visible activity</b> (an activity that is visible to the user
+ * but not in the foreground, such as one sitting behind a foreground dialog
+ * or next to other activities in multi-window mode)
+ * is considered extremely important and will not be killed unless that is
+ * required to keep the foreground activity running.
+ * <li> <p>A <b>background activity</b> (an activity that is not visible to
+ * the user and has been stopped) is no longer critical, so the system may
+ * safely kill its process to reclaim memory for other foreground or
+ * visible processes. If its process needs to be killed, when the user navigates
+ * back to the activity (making it visible on the screen again), its
+ * {@link #onCreate} method will be called with the savedInstanceState it had previously
+ * supplied in {@link #onSaveInstanceState} so that it can restart itself in the same
+ * state as the user last left it.
+ * <li> <p>An <b>empty process</b> is one hosting no activities or other
+ * application components (such as {@link Service} or
+ * {@link android.content.BroadcastReceiver} classes). These are killed very
+ * quickly by the system as memory becomes low. For this reason, any
+ * background operation you do outside of an activity must be executed in the
+ * context of an activity BroadcastReceiver or Service to ensure that the system
+ * knows it needs to keep your process around.
+ * </ol>
+ *
+ * <p>Sometimes an Activity may need to do a long-running operation that exists
+ * independently of the activity lifecycle itself. An example may be a camera
+ * application that allows you to upload a picture to a web site. The upload
+ * may take a long time, and the application should allow the user to leave
+ * the application while it is executing. To accomplish this, your Activity
+ * should start a {@link Service} in which the upload takes place. This allows
+ * the system to properly prioritize your process (considering it to be more
+ * important than other non-visible applications) for the duration of the
+ * upload, independent of whether the original activity is paused, stopped,
+ * or finished.
+ */
+@UiContext
+public class Activity extends ContextThemeWrapper
+ implements LayoutInflater.Factory2,
+ Window.Callback, KeyEvent.Callback,
+ OnCreateContextMenuListener, ComponentCallbacks2,
+ Window.OnWindowDismissedCallback,
+ ContentCaptureManager.ContentCaptureClient {
+ private static final String TAG = "Activity";
+ private static final boolean DEBUG_LIFECYCLE = false;
+
+ /** Standard activity result: operation canceled. */
+ public static final int RESULT_CANCELED = 0;
+ /** Standard activity result: operation succeeded. */
+ public static final int RESULT_OK = -1;
+ /** Start of user-defined activity results. */
+ public static final int RESULT_FIRST_USER = 1;
+
+ /** @hide Task isn't finished when activity is finished */
+ public static final int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
+ /**
+ * @hide Task is finished if the finishing activity is the root of the task. To preserve the
+ * past behavior the task is also removed from recents.
+ */
+ public static final int FINISH_TASK_WITH_ROOT_ACTIVITY = 1;
+ /**
+ * @hide Task is finished along with the finishing activity, but it is not removed from
+ * recents.
+ */
+ public static final int FINISH_TASK_WITH_ACTIVITY = 2;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ static final String FRAGMENTS_TAG = "android:fragments";
+
+ private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
+ private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
+ private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
+ private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
+ private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
+ private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY =
+ "android:hasCurrentPermissionsRequest";
+
+ private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
+ private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
+
+ private static final int LOG_AM_ON_CREATE_CALLED = 30057;
+ private static final int LOG_AM_ON_START_CALLED = 30059;
+ private static final int LOG_AM_ON_RESUME_CALLED = 30022;
+ private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+ private static final int LOG_AM_ON_STOP_CALLED = 30049;
+ private static final int LOG_AM_ON_RESTART_CALLED = 30058;
+ private static final int LOG_AM_ON_DESTROY_CALLED = 30060;
+ private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062;
+ private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
+ private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+ private OnBackInvokedCallback mDefaultBackCallback;
+
+ /**
+ * After {@link Build.VERSION_CODES#TIRAMISU},
+ * {@link #dump(String, FileDescriptor, PrintWriter, String[])} is not called if
+ * {@code dumpsys activity} is called with some special arguments.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @VisibleForTesting
+ private static final long DUMP_IGNORES_SPECIAL_ARGS = 149254050L;
+
+ private static class ManagedDialog {
+ Dialog mDialog;
+ Bundle mArgs;
+ }
+
+ /** @hide */ public static final String DUMP_ARG_AUTOFILL = "--autofill";
+ /** @hide */ public static final String DUMP_ARG_CONTENT_CAPTURE = "--contentcapture";
+ /** @hide */ public static final String DUMP_ARG_TRANSLATION = "--translation";
+ /** @hide */ @TestApi public static final String DUMP_ARG_LIST_DUMPABLES = "--list-dumpables";
+ /** @hide */ @TestApi public static final String DUMP_ARG_DUMP_DUMPABLE = "--dump-dumpable";
+
+ private SparseArray<ManagedDialog> mManagedDialogs;
+
+ // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.
+ @UnsupportedAppUsage
+ private Instrumentation mInstrumentation;
+ @UnsupportedAppUsage
+ private IBinder mToken;
+ private IBinder mAssistToken;
+ private IBinder mShareableActivityToken;
+ @UnsupportedAppUsage
+ private int mIdent;
+ @UnsupportedAppUsage
+ /*package*/ String mEmbeddedID;
+ @UnsupportedAppUsage
+ private Application mApplication;
+ @UnsupportedAppUsage
+ /*package*/ Intent mIntent;
+ @UnsupportedAppUsage
+ /*package*/ String mReferrer;
+ @UnsupportedAppUsage
+ private ComponentName mComponent;
+ @UnsupportedAppUsage
+ /*package*/ ActivityInfo mActivityInfo;
+ @UnsupportedAppUsage
+ /*package*/ ActivityThread mMainThread;
+ @UnsupportedAppUsage(trackingBug = 137825207, maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@code androidx.fragment.app.Fragment} and "
+ + "{@code androidx.fragment.app.FragmentManager} instead")
+ Activity mParent;
+ @UnsupportedAppUsage
+ boolean mCalled;
+ @UnsupportedAppUsage
+ /*package*/ boolean mResumed;
+ @UnsupportedAppUsage
+ /*package*/ boolean mStopped;
+ @UnsupportedAppUsage
+ boolean mFinished;
+ boolean mStartedActivity;
+ @UnsupportedAppUsage
+ private boolean mDestroyed;
+ private boolean mDoReportFullyDrawn = true;
+ private boolean mRestoredFromBundle;
+
+ /** {@code true} if the activity lifecycle is in a state which supports picture-in-picture.
+ * This only affects the client-side exception, the actual state check still happens in AMS. */
+ private boolean mCanEnterPictureInPicture = false;
+ /** true if the activity is being destroyed in order to recreate it with a new configuration */
+ /*package*/ boolean mChangingConfigurations = false;
+ @UnsupportedAppUsage
+ /*package*/ int mConfigChangeFlags;
+ @UnsupportedAppUsage
+ /*package*/ Configuration mCurrentConfig = Configuration.EMPTY;
+ private SearchManager mSearchManager;
+ private MenuInflater mMenuInflater;
+
+ /** The content capture manager. Access via {@link #getContentCaptureManager()}. */
+ @Nullable private ContentCaptureManager mContentCaptureManager;
+
+ private final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
+ new ArrayList<Application.ActivityLifecycleCallbacks>();
+
+ static final class NonConfigurationInstances {
+ Object activity;
+ HashMap<String, Object> children;
+ FragmentManagerNonConfig fragments;
+ ArrayMap<String, LoaderManager> loaders;
+ VoiceInteractor voiceInteractor;
+ }
+ @UnsupportedAppUsage
+ /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
+
+ @UnsupportedAppUsage
+ private Window mWindow;
+
+ @UnsupportedAppUsage
+ private WindowManager mWindowManager;
+ /*package*/ View mDecor = null;
+ @UnsupportedAppUsage
+ /*package*/ boolean mWindowAdded = false;
+ /*package*/ boolean mVisibleFromServer = false;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ /*package*/ boolean mVisibleFromClient = true;
+ /*package*/ ActionBar mActionBar = null;
+ private boolean mEnableDefaultActionBarUp;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ VoiceInteractor mVoiceInteractor;
+
+ @UnsupportedAppUsage
+ private CharSequence mTitle;
+ private int mTitleColor = 0;
+
+ // we must have a handler before the FragmentController is constructed
+ @UnsupportedAppUsage
+ final Handler mHandler = new Handler();
+ @UnsupportedAppUsage
+ final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
+
+ /** The options for scene transition. */
+ ActivityOptions mPendingOptions;
+
+ /** Whether this activity was launched from a bubble. **/
+ boolean mLaunchedFromBubble;
+
+ private static final class ManagedCursor {
+ ManagedCursor(Cursor cursor) {
+ mCursor = cursor;
+ mReleased = false;
+ mUpdated = false;
+ }
+
+ private final Cursor mCursor;
+ private boolean mReleased;
+ private boolean mUpdated;
+ }
+
+ @GuardedBy("mManagedCursors")
+ private final ArrayList<ManagedCursor> mManagedCursors = new ArrayList<>();
+
+ @GuardedBy("this")
+ @UnsupportedAppUsage
+ int mResultCode = RESULT_CANCELED;
+ @GuardedBy("this")
+ @UnsupportedAppUsage
+ Intent mResultData = null;
+
+ private TranslucentConversionListener mTranslucentCallback;
+ private boolean mChangeCanvasToTranslucent;
+
+ private SearchEvent mSearchEvent;
+
+ private boolean mTitleReady = false;
+ private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+
+ private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
+ private SpannableStringBuilder mDefaultKeySsb = null;
+
+ private ActivityManager.TaskDescription mTaskDescription =
+ new ActivityManager.TaskDescription();
+
+ protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
+
+ @SuppressWarnings("unused")
+ private final Object mInstanceTracker = StrictMode.trackActivity(this);
+
+ private Thread mUiThread;
+
+ @UnsupportedAppUsage
+ ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
+ SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK;
+ SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
+
+ private boolean mHasCurrentPermissionsRequest;
+
+ /** The autofill client controller. Always access via {@link #getAutofillClientController()}. */
+ private AutofillClientController mAutofillClientController;
+
+ /** @hide */
+ boolean mEnterAnimationComplete;
+
+ private boolean mIsInMultiWindowMode;
+ /** @hide */
+ boolean mIsInPictureInPictureMode;
+
+ /** @hide */
+ @IntDef(prefix = { "FULLSCREEN_REQUEST_" }, value = {
+ FULLSCREEN_MODE_REQUEST_EXIT,
+ FULLSCREEN_MODE_REQUEST_ENTER
+ })
+ public @interface FullscreenModeRequest {}
+
+ /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the
+ * requested fullscreen mode and restore to the previous multi-window mode.
+ */
+ public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
+ /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request enter
+ * fullscreen mode from multi-window mode.
+ */
+ public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "OVERRIDE_TRANSITION_" }, value = {
+ OVERRIDE_TRANSITION_OPEN,
+ OVERRIDE_TRANSITION_CLOSE
+ })
+ public @interface OverrideTransition {}
+
+ /**
+ * Request type of {@link #overrideActivityTransition(int, int, int)} or
+ * {@link #overrideActivityTransition(int, int, int, int)}, to override the
+ * opening transition.
+ */
+ public static final int OVERRIDE_TRANSITION_OPEN = 0;
+ /**
+ * Request type of {@link #overrideActivityTransition(int, int, int)} or
+ * {@link #overrideActivityTransition(int, int, int, int)}, to override the
+ * closing transition.
+ */
+ public static final int OVERRIDE_TRANSITION_CLOSE = 1;
+ private boolean mShouldDockBigOverlays;
+
+ private UiTranslationController mUiTranslationController;
+
+ private SplashScreen mSplashScreen;
+
+ @Nullable
+ private DumpableContainerImpl mDumpableContainer;
+
+ private ComponentCallbacksController mCallbacksController;
+
+ @Nullable private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+ private ScreenCaptureCallbackHandler mScreenCaptureCallbackHandler;
+
+ private final WindowControllerCallback mWindowControllerCallback =
+ new WindowControllerCallback() {
+ /**
+ * Moves the activity between {@link WindowConfiguration#WINDOWING_MODE_FREEFORM} windowing
+ * mode and {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+ *
+ * @hide
+ */
+ @Override
+ public void toggleFreeformWindowingMode() {
+ ActivityClient.getInstance().toggleFreeformWindowingMode(mToken);
+ }
+
+ /**
+ * Puts the activity in picture-in-picture mode if the activity supports.
+ * @see android.R.attr#supportsPictureInPicture
+ * @hide
+ */
+ @Override
+ public void enterPictureInPictureModeIfPossible() {
+ if (mActivityInfo.supportsPictureInPicture()) {
+ enterPictureInPictureMode();
+ }
+ }
+
+ @Override
+ public boolean isTaskRoot() {
+ return ActivityClient.getInstance().getTaskForActivity(
+ mToken, true /* onlyRoot */) >= 0;
+ }
+
+ /**
+ * Update the forced status bar color.
+ * @hide
+ */
+ @Override
+ public void updateStatusBarColor(int color) {
+ mTaskDescription.setStatusBarColor(color);
+ setTaskDescription(mTaskDescription);
+ }
+
+ /**
+ * Update the forced navigation bar color.
+ * @hide
+ */
+ @Override
+ public void updateNavigationBarColor(int color) {
+ mTaskDescription.setNavigationBarColor(color);
+ setTaskDescription(mTaskDescription);
+ }
+
+ };
+
+ private static native String getDlWarning();
+
+ /** Return the intent that started this activity. */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Change the intent returned by {@link #getIntent}. This holds a
+ * reference to the given intent; it does not copy it. Often used in
+ * conjunction with {@link #onNewIntent}.
+ *
+ * @param newIntent The new Intent object to return from getIntent
+ *
+ * @see #getIntent
+ * @see #onNewIntent
+ */
+ public void setIntent(Intent newIntent) {
+ mIntent = newIntent;
+ }
+
+ /**
+ * Sets the {@link android.content.LocusId} for this activity. The locus id
+ * helps identify different instances of the same {@code Activity} class.
+ * <p> For example, a locus id based on a specific conversation could be set on a
+ * conversation app's chat {@code Activity}. The system can then use this locus id
+ * along with app's contents to provide ranking signals in various UI surfaces
+ * including sharing, notifications, shortcuts and so on.
+ * <p> It is recommended to set the same locus id in the shortcut's locus id using
+ * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(android.content.LocusId)
+ * setLocusId}
+ * so that the system can learn appropriate ranking signals linking the activity's
+ * locus id with the matching shortcut.
+ *
+ * @param locusId a unique, stable id that identifies this {@code Activity} instance. LocusId
+ * is an opaque ID that links this Activity's state to different Android concepts:
+ * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(android.content.LocusId)
+ * setLocusId}. LocusID is null by default or if you explicitly reset it.
+ * @param bundle extras set or updated as part of this locus context. This may help provide
+ * additional metadata such as URLs, conversation participants specific to this
+ * {@code Activity}'s context. Bundle can be null if additional metadata is not needed.
+ * Bundle should always be null for null locusId.
+ *
+ * @see android.view.contentcapture.ContentCaptureManager
+ * @see android.view.contentcapture.ContentCaptureContext
+ */
+ public void setLocusContext(@Nullable LocusId locusId, @Nullable Bundle bundle) {
+ try {
+ ActivityManager.getService().setActivityLocusContext(mComponent, locusId, mToken);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ // If locusId is not null pass it to the Content Capture.
+ if (locusId != null) {
+ setLocusContextToContentCapture(locusId, bundle);
+ }
+ }
+
+ /** Return the application that owns this activity. */
+ public final Application getApplication() {
+ return mApplication;
+ }
+
+ /** Is this activity embedded inside of another activity? */
+ public final boolean isChild() {
+ return mParent != null;
+ }
+
+ /** Return the parent activity if this view is an embedded child. */
+ public final Activity getParent() {
+ return mParent;
+ }
+
+ /** Retrieve the window manager for showing custom windows. */
+ public WindowManager getWindowManager() {
+ return mWindowManager;
+ }
+
+ /**
+ * Retrieve the current {@link android.view.Window} for the activity.
+ * This can be used to directly access parts of the Window API that
+ * are not available through Activity/Screen.
+ *
+ * @return Window The current window, or null if the activity is not
+ * visual.
+ */
+ public Window getWindow() {
+ return mWindow;
+ }
+
+ /**
+ * Return the LoaderManager for this activity, creating it if needed.
+ *
+ * @deprecated Use {@link androidx.fragment.app.FragmentActivity#getSupportLoaderManager()}
+ */
+ @Deprecated
+ public LoaderManager getLoaderManager() {
+ return mFragments.getLoaderManager();
+ }
+
+ /**
+ * Calls {@link android.view.Window#getCurrentFocus} on the
+ * Window of this Activity to return the currently focused view.
+ *
+ * @return View The current View with focus or null.
+ *
+ * @see #getWindow
+ * @see android.view.Window#getCurrentFocus
+ */
+ @Nullable
+ public View getCurrentFocus() {
+ return mWindow != null ? mWindow.getCurrentFocus() : null;
+ }
+
+ /**
+ * (Creates, sets, and ) returns the content capture manager
+ *
+ * @return The content capture manager
+ */
+ @Nullable private ContentCaptureManager getContentCaptureManager() {
+ // ContextCapture disabled for system apps
+ if (!UserHandle.isApp(myUid())) return null;
+ if (mContentCaptureManager == null) {
+ mContentCaptureManager = getSystemService(ContentCaptureManager.class);
+ }
+ return mContentCaptureManager;
+ }
+
+ /** @hide */ private static final int CONTENT_CAPTURE_START = 1;
+ /** @hide */ private static final int CONTENT_CAPTURE_RESUME = 2;
+ /** @hide */ private static final int CONTENT_CAPTURE_PAUSE = 3;
+ /** @hide */ private static final int CONTENT_CAPTURE_STOP = 4;
+
+ /** @hide */
+ @IntDef(prefix = { "CONTENT_CAPTURE_" }, value = {
+ CONTENT_CAPTURE_START,
+ CONTENT_CAPTURE_RESUME,
+ CONTENT_CAPTURE_PAUSE,
+ CONTENT_CAPTURE_STOP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ContentCaptureNotificationType{}
+
+ private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) {
+ switch (type) {
+ case CONTENT_CAPTURE_START:
+ return "START";
+ case CONTENT_CAPTURE_RESUME:
+ return "RESUME";
+ case CONTENT_CAPTURE_PAUSE:
+ return "PAUSE";
+ case CONTENT_CAPTURE_STOP:
+ return "STOP";
+ default:
+ return "UNKNOW-" + type;
+ }
+ }
+
+ private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for "
+ + mComponent.toShortString());
+ }
+ try {
+ final ContentCaptureManager cm = getContentCaptureManager();
+ if (cm == null) return;
+
+ switch (type) {
+ case CONTENT_CAPTURE_START:
+ //TODO(b/111276913): decide whether the InteractionSessionId should be
+ // saved / restored in the activity bundle - probably not
+ final Window window = getWindow();
+ if (window != null) {
+ cm.updateWindowAttributes(window.getAttributes());
+ }
+ cm.onActivityCreated(mToken, mShareableActivityToken, getComponentName());
+ break;
+ case CONTENT_CAPTURE_RESUME:
+ cm.onActivityResumed();
+ break;
+ case CONTENT_CAPTURE_PAUSE:
+ cm.onActivityPaused();
+ break;
+ case CONTENT_CAPTURE_STOP:
+ cm.onActivityDestroyed();
+ break;
+ default:
+ Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+
+ private void setLocusContextToContentCapture(LocusId locusId, @Nullable Bundle bundle) {
+ final ContentCaptureManager cm = getContentCaptureManager();
+ if (cm == null) return;
+
+ ContentCaptureContext.Builder contentCaptureContextBuilder =
+ new ContentCaptureContext.Builder(locusId);
+ if (bundle != null) {
+ contentCaptureContextBuilder.setExtras(bundle);
+ }
+ cm.getMainContentCaptureSession().setContentCaptureContext(
+ contentCaptureContextBuilder.build());
+ }
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ if (newBase != null) {
+ newBase.setAutofillClient(getAutofillClient());
+ newBase.setContentCaptureOptions(getContentCaptureOptions());
+ }
+ }
+
+ /** @hide */
+ @Override
+ public final AutofillClient getAutofillClient() {
+ return getAutofillClientController();
+ }
+
+ private AutofillClientController getAutofillClientController() {
+ if (mAutofillClientController == null) {
+ mAutofillClientController = new AutofillClientController(this);
+ }
+ return mAutofillClientController;
+ }
+
+ /** @hide */
+ @Override
+ public final ContentCaptureClient getContentCaptureClient() {
+ return this;
+ }
+
+ /**
+ * Register an {@link Application.ActivityLifecycleCallbacks} instance that receives
+ * lifecycle callbacks for only this Activity.
+ * <p>
+ * In relation to any
+ * {@link Application#registerActivityLifecycleCallbacks Application registered callbacks},
+ * the callbacks registered here will always occur nested within those callbacks. This means:
+ * <ul>
+ * <li>Pre events will first be sent to Application registered callbacks, then to callbacks
+ * registered here.</li>
+ * <li>{@link Application.ActivityLifecycleCallbacks#onActivityCreated(Activity, Bundle)},
+ * {@link Application.ActivityLifecycleCallbacks#onActivityStarted(Activity)}, and
+ * {@link Application.ActivityLifecycleCallbacks#onActivityResumed(Activity)} will
+ * be sent first to Application registered callbacks, then to callbacks registered here.
+ * For all other events, callbacks registered here will be sent first.</li>
+ * <li>Post events will first be sent to callbacks registered here, then to
+ * Application registered callbacks.</li>
+ * </ul>
+ * <p>
+ * If multiple callbacks are registered here, they receive events in a first in (up through
+ * {@link Application.ActivityLifecycleCallbacks#onActivityPostResumed}, last out
+ * ordering.
+ * <p>
+ * It is strongly recommended to register this in the constructor of your Activity to ensure
+ * you get all available callbacks. As this callback is associated with only this Activity,
+ * it is not usually necessary to {@link #unregisterActivityLifecycleCallbacks unregister} it
+ * unless you specifically do not want to receive further lifecycle callbacks.
+ *
+ * @param callback The callback instance to register
+ */
+ public void registerActivityLifecycleCallbacks(
+ @NonNull Application.ActivityLifecycleCallbacks callback) {
+ synchronized (mActivityLifecycleCallbacks) {
+ mActivityLifecycleCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Unregister an {@link Application.ActivityLifecycleCallbacks} previously registered
+ * with {@link #registerActivityLifecycleCallbacks}. It will not receive any further
+ * callbacks.
+ *
+ * @param callback The callback instance to unregister
+ * @see #registerActivityLifecycleCallbacks
+ */
+ public void unregisterActivityLifecycleCallbacks(
+ @NonNull Application.ActivityLifecycleCallbacks callback) {
+ synchronized (mActivityLifecycleCallbacks) {
+ mActivityLifecycleCallbacks.remove(callback);
+ }
+ }
+
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)
+ && mCallbacksController == null) {
+ mCallbacksController = new ComponentCallbacksController();
+ }
+ if (mCallbacksController != null) {
+ mCallbacksController.registerCallbacks(callback);
+ } else {
+ super.registerComponentCallbacks(callback);
+ }
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ if (mCallbacksController != null) {
+ mCallbacksController.unregisterCallbacks(callback);
+ } else {
+ super.unregisterComponentCallbacks(callback);
+ }
+ }
+
+ private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) {
+ getApplication().dispatchActivityPreCreated(this, savedInstanceState);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreCreated(this,
+ savedInstanceState);
+ }
+ }
+ }
+
+ private void dispatchActivityCreated(@Nullable Bundle savedInstanceState) {
+ getApplication().dispatchActivityCreated(this, savedInstanceState);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(this,
+ savedInstanceState);
+ }
+ }
+ }
+
+ private void dispatchActivityPostCreated(@Nullable Bundle savedInstanceState) {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPostCreated(this,
+ savedInstanceState);
+ }
+ }
+ getApplication().dispatchActivityPostCreated(this, savedInstanceState);
+ }
+
+ private void dispatchActivityPreStarted() {
+ getApplication().dispatchActivityPreStarted(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStarted(this);
+ }
+ }
+ }
+
+ private void dispatchActivityStarted() {
+ getApplication().dispatchActivityStarted(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStarted(this);
+ }
+ }
+ }
+
+ private void dispatchActivityPostStarted() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityPostStarted(this);
+ }
+ }
+ getApplication().dispatchActivityPostStarted(this);
+ }
+
+ private void dispatchActivityPreResumed() {
+ getApplication().dispatchActivityPreResumed(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreResumed(this);
+ }
+ }
+ }
+
+ private void dispatchActivityResumed() {
+ getApplication().dispatchActivityResumed(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityResumed(this);
+ }
+ }
+ }
+
+ private void dispatchActivityPostResumed() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPostResumed(this);
+ }
+ }
+ getApplication().dispatchActivityPostResumed(this);
+ }
+
+ private void dispatchActivityPrePaused() {
+ getApplication().dispatchActivityPrePaused(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPrePaused(this);
+ }
+ }
+ }
+
+ private void dispatchActivityPaused() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPaused(this);
+ }
+ }
+ getApplication().dispatchActivityPaused(this);
+ }
+
+ private void dispatchActivityPostPaused() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPostPaused(this);
+ }
+ }
+ getApplication().dispatchActivityPostPaused(this);
+ }
+
+ private void dispatchActivityPreStopped() {
+ getApplication().dispatchActivityPreStopped(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStopped(this);
+ }
+ }
+ }
+
+ private void dispatchActivityStopped() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStopped(this);
+ }
+ }
+ getApplication().dispatchActivityStopped(this);
+ }
+
+ private void dispatchActivityPostStopped() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityPostStopped(this);
+ }
+ }
+ getApplication().dispatchActivityPostStopped(this);
+ }
+
+ private void dispatchActivityPreSaveInstanceState(@NonNull Bundle outState) {
+ getApplication().dispatchActivityPreSaveInstanceState(this, outState);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityPreSaveInstanceState(this, outState);
+ }
+ }
+ }
+
+ private void dispatchActivitySaveInstanceState(@NonNull Bundle outState) {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivitySaveInstanceState(this, outState);
+ }
+ }
+ getApplication().dispatchActivitySaveInstanceState(this, outState);
+ }
+
+ private void dispatchActivityPostSaveInstanceState(@NonNull Bundle outState) {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityPostSaveInstanceState(this, outState);
+ }
+ }
+ getApplication().dispatchActivityPostSaveInstanceState(this, outState);
+ }
+
+ private void dispatchActivityPreDestroyed() {
+ getApplication().dispatchActivityPreDestroyed(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityPreDestroyed(this);
+ }
+ }
+ }
+
+ private void dispatchActivityDestroyed() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityDestroyed(this);
+ }
+ }
+ getApplication().dispatchActivityDestroyed(this);
+ }
+
+ private void dispatchActivityPostDestroyed() {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = callbacks.length - 1; i >= 0; i--) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityPostDestroyed(this);
+ }
+ }
+ getApplication().dispatchActivityPostDestroyed(this);
+ }
+
+ private void dispatchActivityConfigurationChanged() {
+ // In case the new config comes before mApplication is assigned.
+ if (getApplication() != null) {
+ getApplication().dispatchActivityConfigurationChanged(this);
+ }
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityConfigurationChanged(this);
+ }
+ }
+ }
+
+ private Object[] collectActivityLifecycleCallbacks() {
+ Object[] callbacks = null;
+ synchronized (mActivityLifecycleCallbacks) {
+ if (mActivityLifecycleCallbacks.size() > 0) {
+ callbacks = mActivityLifecycleCallbacks.toArray();
+ }
+ }
+ return callbacks;
+ }
+
+ private void notifyVoiceInteractionManagerServiceActivityEvent(
+ @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
+ if (mVoiceInteractionManagerService == null) {
+ mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ if (mVoiceInteractionManagerService == null) {
+ Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+ + "VoiceInteractionManagerService");
+ return;
+ }
+ }
+ try {
+ mVoiceInteractionManagerService.notifyActivityEventChanged(mToken, type);
+ } catch (RemoteException e) {
+ // Empty
+ }
+ }
+
+ /**
+ * Called when the activity is starting. This is where most initialization
+ * should go: calling {@link #setContentView(int)} to inflate the
+ * activity's UI, using {@link #findViewById} to programmatically interact
+ * with widgets in the UI, calling
+ * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve
+ * cursors for data being displayed, etc.
+ *
+ * <p>You can call {@link #finish} from within this function, in
+ * which case onDestroy() will be immediately called after {@link #onCreate} without any of the
+ * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc)
+ * executing.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @param savedInstanceState If the activity is being re-initialized after
+ * previously being shut down then this Bundle contains the data it most
+ * recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b>
+ *
+ * @see #onStart
+ * @see #onSaveInstanceState
+ * @see #onRestoreInstanceState
+ * @see #onPostCreate
+ */
+ @MainThread
+ @CallSuper
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
+
+ if (mLastNonConfigurationInstances != null) {
+ mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
+ }
+ if (mActivityInfo.parentActivityName != null) {
+ if (mActionBar == null) {
+ mEnableDefaultActionBarUp = true;
+ } else {
+ mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ if (savedInstanceState != null) {
+ getAutofillClientController().onActivityCreated(savedInstanceState);
+
+ Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
+ mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
+ ? mLastNonConfigurationInstances.fragments : null);
+ }
+ mFragments.dispatchCreate();
+ dispatchActivityCreated(savedInstanceState);
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.attachActivity(this);
+ }
+ mRestoredFromBundle = savedInstanceState != null;
+ mCalled = true;
+
+ boolean aheadOfTimeBack = WindowOnBackInvokedDispatcher
+ .isOnBackInvokedCallbackEnabled(this);
+ if (aheadOfTimeBack) {
+ // Add onBackPressed as default back behavior.
+ mDefaultBackCallback = this::onBackInvoked;
+ getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
+ }
+ }
+
+ /**
+ * Get the interface that activity use to talk to the splash screen.
+ * @see SplashScreen
+ */
+ public final @NonNull SplashScreen getSplashScreen() {
+ return getOrCreateSplashScreen();
+ }
+
+ private SplashScreen getOrCreateSplashScreen() {
+ synchronized (this) {
+ if (mSplashScreen == null) {
+ mSplashScreen = new SplashScreen.SplashScreenImpl(this);
+ }
+ return mSplashScreen;
+ }
+ }
+
+ /**
+ * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
+ * the attribute {@link android.R.attr#persistableMode} set to
+ * <code>persistAcrossReboots</code>.
+ *
+ * @param savedInstanceState if the activity is being re-initialized after
+ * previously being shut down then this Bundle contains the data it most
+ * recently supplied in {@link #onSaveInstanceState}.
+ * <b><i>Note: Otherwise it is null.</i></b>
+ * @param persistentState if the activity is being re-initialized after
+ * previously being shut down or powered off then this Bundle contains the data it most
+ * recently supplied to outPersistentState in {@link #onSaveInstanceState}.
+ * <b><i>Note: Otherwise it is null.</i></b>
+ *
+ * @see #onCreate(android.os.Bundle)
+ * @see #onStart
+ * @see #onSaveInstanceState
+ * @see #onRestoreInstanceState
+ * @see #onPostCreate
+ */
+ public void onCreate(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ onCreate(savedInstanceState);
+ }
+
+ /**
+ * The hook for {@link ActivityThread} to restore the state of this activity.
+ *
+ * Calls {@link #onSaveInstanceState(android.os.Bundle)} and
+ * {@link #restoreManagedDialogs(android.os.Bundle)}.
+ *
+ * @param savedInstanceState contains the saved state
+ */
+ final void performRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ onRestoreInstanceState(savedInstanceState);
+ restoreManagedDialogs(savedInstanceState);
+ }
+
+ /**
+ * The hook for {@link ActivityThread} to restore the state of this activity.
+ *
+ * Calls {@link #onSaveInstanceState(android.os.Bundle)} and
+ * {@link #restoreManagedDialogs(android.os.Bundle)}.
+ *
+ * @param savedInstanceState contains the saved state
+ * @param persistentState contains the persistable saved state
+ */
+ final void performRestoreInstanceState(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ onRestoreInstanceState(savedInstanceState, persistentState);
+ if (savedInstanceState != null) {
+ restoreManagedDialogs(savedInstanceState);
+ }
+ }
+
+ /**
+ * This method is called after {@link #onStart} when the activity is
+ * being re-initialized from a previously saved state, given here in
+ * <var>savedInstanceState</var>. Most implementations will simply use {@link #onCreate}
+ * to restore their state, but it is sometimes convenient to do it here
+ * after all of the initialization has been done or to allow subclasses to
+ * decide whether to use your default implementation. The default
+ * implementation of this method performs a restore of any view state that
+ * had previously been frozen by {@link #onSaveInstanceState}.
+ *
+ * <p>This method is called between {@link #onStart} and
+ * {@link #onPostCreate}. This method is called only when recreating
+ * an activity; the method isn't invoked if {@link #onStart} is called for
+ * any other reason.</p>
+ *
+ * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
+ *
+ * @see #onCreate
+ * @see #onPostCreate
+ * @see #onResume
+ * @see #onSaveInstanceState
+ */
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ if (mWindow != null) {
+ Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
+ if (windowState != null) {
+ mWindow.restoreHierarchyState(windowState);
+ }
+ }
+ }
+
+ /**
+ * This is the same as {@link #onRestoreInstanceState(Bundle)} but is called for activities
+ * created with the attribute {@link android.R.attr#persistableMode} set to
+ * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed
+ * came from the restored PersistableBundle first
+ * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
+ *
+ * <p>This method is called between {@link #onStart} and
+ * {@link #onPostCreate}.
+ *
+ * <p>If this method is called {@link #onRestoreInstanceState(Bundle)} will not be called.
+ *
+ * <p>At least one of {@code savedInstanceState} or {@code persistentState} will not be null.
+ *
+ * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}
+ * or null.
+ * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}
+ * or null.
+ *
+ * @see #onRestoreInstanceState(Bundle)
+ * @see #onCreate
+ * @see #onPostCreate
+ * @see #onResume
+ * @see #onSaveInstanceState
+ */
+ public void onRestoreInstanceState(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ if (savedInstanceState != null) {
+ onRestoreInstanceState(savedInstanceState);
+ }
+ }
+
+ /**
+ * Restore the state of any saved managed dialogs.
+ *
+ * @param savedInstanceState The bundle to restore from.
+ */
+ private void restoreManagedDialogs(Bundle savedInstanceState) {
+ final Bundle b = savedInstanceState.getBundle(SAVED_DIALOGS_TAG);
+ if (b == null) {
+ return;
+ }
+
+ final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY);
+ final int numDialogs = ids.length;
+ mManagedDialogs = new SparseArray<ManagedDialog>(numDialogs);
+ for (int i = 0; i < numDialogs; i++) {
+ final Integer dialogId = ids[i];
+ Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
+ if (dialogState != null) {
+ // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
+ // so tell createDialog() not to do it, otherwise we get an exception
+ final ManagedDialog md = new ManagedDialog();
+ md.mArgs = b.getBundle(savedDialogArgsKeyFor(dialogId));
+ md.mDialog = createDialog(dialogId, dialogState, md.mArgs);
+ if (md.mDialog != null) {
+ mManagedDialogs.put(dialogId, md);
+ onPrepareDialog(dialogId, md.mDialog, md.mArgs);
+ md.mDialog.onRestoreInstanceState(dialogState);
+ }
+ }
+ }
+ }
+
+ private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) {
+ final Dialog dialog = onCreateDialog(dialogId, args);
+ if (dialog == null) {
+ return null;
+ }
+ dialog.dispatchOnCreate(state);
+ return dialog;
+ }
+
+ private static String savedDialogKeyFor(int key) {
+ return SAVED_DIALOG_KEY_PREFIX + key;
+ }
+
+ private static String savedDialogArgsKeyFor(int key) {
+ return SAVED_DIALOG_ARGS_KEY_PREFIX + key;
+ }
+
+ /**
+ * Called when activity start-up is complete (after {@link #onStart}
+ * and {@link #onRestoreInstanceState} have been called). Applications will
+ * generally not implement this method; it is intended for system
+ * classes to do final initialization after application code has run.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @param savedInstanceState If the activity is being re-initialized after
+ * previously being shut down then this Bundle contains the data it most
+ * recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b>
+ * @see #onCreate
+ */
+ @CallSuper
+ protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+ if (!isChild()) {
+ mTitleReady = true;
+ onTitleChanged(getTitle(), getTitleColor());
+ }
+
+ mCalled = true;
+
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START);
+ }
+
+ /**
+ * This is the same as {@link #onPostCreate(Bundle)} but is called for activities
+ * created with the attribute {@link android.R.attr#persistableMode} set to
+ * <code>persistAcrossReboots</code>.
+ *
+ * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState}
+ * @param persistentState The data caming from the PersistableBundle first
+ * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
+ *
+ * @see #onCreate
+ */
+ public void onPostCreate(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ onPostCreate(savedInstanceState);
+ }
+
+ /**
+ * Called after {@link #onCreate} — or after {@link #onRestart} when
+ * the activity had been stopped, but is now again being displayed to the
+ * user. It will usually be followed by {@link #onResume}. This is a good place to begin
+ * drawing visual elements, running animations, etc.
+ *
+ * <p>You can call {@link #finish} from within this function, in
+ * which case {@link #onStop} will be immediately called after {@link #onStart} without the
+ * lifecycle transitions in-between ({@link #onResume}, {@link #onPause}, etc) executing.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onCreate
+ * @see #onStop
+ * @see #onResume
+ */
+ @CallSuper
+ protected void onStart() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
+ mCalled = true;
+
+ mFragments.doLoaderStart();
+
+ dispatchActivityStarted();
+
+ getAutofillClientController().onActivityStarted();
+ }
+
+ /**
+ * Called after {@link #onStop} when the current activity is being
+ * re-displayed to the user (the user has navigated back to it). It will
+ * be followed by {@link #onStart} and then {@link #onResume}.
+ *
+ * <p>For activities that are using raw {@link Cursor} objects (instead of
+ * creating them through
+ * {@link #managedQuery(android.net.Uri , String[], String, String[], String)},
+ * this is usually the place
+ * where the cursor should be requeried (because you had deactivated it in
+ * {@link #onStop}.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onStop
+ * @see #onStart
+ * @see #onResume
+ */
+ @CallSuper
+ protected void onRestart() {
+ mCalled = true;
+ }
+
+ /**
+ * Called when an {@link #onResume} is coming up, prior to other pre-resume callbacks
+ * such as {@link #onNewIntent} and {@link #onActivityResult}. This is primarily intended
+ * to give the activity a hint that its state is no longer saved -- it will generally
+ * be called after {@link #onSaveInstanceState} and prior to the activity being
+ * resumed/started again.
+ *
+ * @deprecated starting with {@link android.os.Build.VERSION_CODES#P} onSaveInstanceState is
+ * called after {@link #onStop}, so this hint isn't accurate anymore: you should consider your
+ * state not saved in between {@code onStart} and {@code onStop} callbacks inclusively.
+ */
+ @Deprecated
+ public void onStateNotSaved() {
+ }
+
+ /**
+ * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or {@link #onPause}. This
+ * is usually a hint for your activity to start interacting with the user, which is a good
+ * indicator that the activity became active and ready to receive input. This sometimes could
+ * also be a transit state toward another resting state. For instance, an activity may be
+ * relaunched to {@link #onPause} due to configuration changes and the activity was visible,
+ * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be
+ * called before {@link #onPause} in this case which honors the activity lifecycle policy and
+ * the activity eventually rests in {@link #onPause}.
+ *
+ * <p>On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good
+ * place to try to open exclusive-access devices or to get access to singleton resources.
+ * Starting with {@link android.os.Build.VERSION_CODES#Q} there can be multiple resumed
+ * activities in the system simultaneously, so {@link #onTopResumedActivityChanged(boolean)}
+ * should be used for that purpose instead.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onRestoreInstanceState
+ * @see #onRestart
+ * @see #onPostResume
+ * @see #onPause
+ * @see #onTopResumedActivityChanged(boolean)
+ */
+ @CallSuper
+ protected void onResume() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
+ dispatchActivityResumed();
+ mActivityTransitionState.onResume(this);
+ getAutofillClientController().onActivityResumed();
+
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_RESUME);
+
+ mCalled = true;
+ }
+
+ /**
+ * Called when activity resume is complete (after {@link #onResume} has
+ * been called). Applications will generally not implement this method;
+ * it is intended for system classes to do final setup after application
+ * resume code has run.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onResume
+ */
+ @CallSuper
+ protected void onPostResume() {
+ final Window win = getWindow();
+ if (win != null) win.makeActive();
+ if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+
+ // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't
+ // allow any binder call in onResume, we call this method in onPostResume.
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+
+ mCalled = true;
+ }
+
+ /**
+ * Called when activity gets or loses the top resumed position in the system.
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} multiple activities can be resumed
+ * at the same time in multi-window and multi-display modes. This callback should be used
+ * instead of {@link #onResume()} as an indication that the activity can try to open
+ * exclusive-access devices like camera.</p>
+ *
+ * <p>It will always be delivered after the activity was resumed and before it is paused. In
+ * some cases it might be skipped and activity can go straight from {@link #onResume()} to
+ * {@link #onPause()} without receiving the top resumed state.</p>
+ *
+ * @param isTopResumedActivity {@code true} if it's the topmost resumed activity in the system,
+ * {@code false} otherwise. A call with this as {@code true} will
+ * always be followed by another one with {@code false}.
+ *
+ * @see #onResume()
+ * @see #onPause()
+ * @see #onWindowFocusChanged(boolean)
+ */
+ public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+ }
+
+ final void performTopResumedActivityChanged(boolean isTopResumedActivity, String reason) {
+ onTopResumedActivityChanged(isTopResumedActivity);
+
+ if (isTopResumedActivity) {
+ EventLogTags.writeWmOnTopResumedGainedCalled(mIdent, getComponentName().getClassName(),
+ reason);
+ } else {
+ EventLogTags.writeWmOnTopResumedLostCalled(mIdent, getComponentName().getClassName(),
+ reason);
+ }
+ }
+
+ void setVoiceInteractor(IVoiceInteractor voiceInteractor) {
+ if (mVoiceInteractor != null) {
+ final Request[] requests = mVoiceInteractor.getActiveRequests();
+ if (requests != null) {
+ for (Request activeRequest : mVoiceInteractor.getActiveRequests()) {
+ activeRequest.cancel();
+ activeRequest.clear();
+ }
+ }
+ }
+ if (voiceInteractor == null) {
+ mVoiceInteractor = null;
+ } else {
+ mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+ Looper.myLooper());
+ }
+ }
+
+ /**
+ * Returns the next autofill ID that is unique in the activity
+ *
+ * <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned
+ * will be unique.
+ *
+ * {@hide}
+ */
+ @Override
+ public int getNextAutofillId() {
+ return getAutofillClientController().getNextAutofillId();
+ }
+
+ /**
+ * Check whether this activity is running as part of a voice interaction with the user.
+ * If true, it should perform its interaction with the user through the
+ * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+ */
+ public boolean isVoiceInteraction() {
+ return mVoiceInteractor != null;
+ }
+
+ /**
+ * Like {@link #isVoiceInteraction}, but only returns {@code true} if this is also the root
+ * of a voice interaction. That is, returns {@code true} if this activity was directly
+ * started by the voice interaction service as the initiation of a voice interaction.
+ * Otherwise, for example if it was started by another activity while under voice
+ * interaction, returns {@code false}.
+ * If the activity {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode} is
+ * {@code singleTask}, it forces the activity to launch in a new task, separate from the one
+ * that started it. Therefore, there is no longer a relationship between them, and
+ * {@link #isVoiceInteractionRoot()} return {@code false} in this case.
+ */
+ public boolean isVoiceInteractionRoot() {
+ return mVoiceInteractor != null
+ && ActivityClient.getInstance().isRootVoiceInteraction(mToken);
+ }
+
+ /**
+ * Retrieve the active {@link VoiceInteractor} that the user is going through to
+ * interact with this activity.
+ */
+ public VoiceInteractor getVoiceInteractor() {
+ return mVoiceInteractor;
+ }
+
+ /**
+ * Queries whether the currently enabled voice interaction service supports returning
+ * a voice interactor for use by the activity. This is valid only for the duration of the
+ * activity.
+ *
+ * @return whether the current voice interaction service supports local voice interaction
+ */
+ public boolean isLocalVoiceInteractionSupported() {
+ try {
+ return ActivityTaskManager.getService().supportsLocalVoiceInteraction();
+ } catch (RemoteException re) {
+ }
+ return false;
+ }
+
+ /**
+ * Starts a local voice interaction session. When ready,
+ * {@link #onLocalVoiceInteractionStarted()} is called. You can pass a bundle of private options
+ * to the registered voice interaction service.
+ * @param privateOptions a Bundle of private arguments to the current voice interaction service
+ */
+ public void startLocalVoiceInteraction(Bundle privateOptions) {
+ ActivityClient.getInstance().startLocalVoiceInteraction(mToken, privateOptions);
+ }
+
+ /**
+ * Callback to indicate that {@link #startLocalVoiceInteraction(Bundle)} has resulted in a
+ * voice interaction session being started. You can now retrieve a voice interactor using
+ * {@link #getVoiceInteractor()}.
+ */
+ public void onLocalVoiceInteractionStarted() {
+ }
+
+ /**
+ * Callback to indicate that the local voice interaction has stopped either
+ * because it was requested through a call to {@link #stopLocalVoiceInteraction()}
+ * or because it was canceled by the user. The previously acquired {@link VoiceInteractor}
+ * is no longer valid after this.
+ */
+ public void onLocalVoiceInteractionStopped() {
+ }
+
+ /**
+ * Request to terminate the current voice interaction that was previously started
+ * using {@link #startLocalVoiceInteraction(Bundle)}. When the interaction is
+ * terminated, {@link #onLocalVoiceInteractionStopped()} will be called.
+ */
+ public void stopLocalVoiceInteraction() {
+ ActivityClient.getInstance().stopLocalVoiceInteraction(mToken);
+ }
+
+ /**
+ * This is called for activities that set launchMode to "singleTop" in
+ * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
+ * flag when calling {@link #startActivity}. In either case, when the
+ * activity is re-launched while at the top of the activity stack instead
+ * of a new instance of the activity being started, onNewIntent() will be
+ * called on the existing instance with the Intent that was used to
+ * re-launch it.
+ *
+ * <p>An activity can never receive a new intent in the resumed state. You can count on
+ * {@link #onResume} being called after this method, though not necessarily immediately after
+ * the completion this callback. If the activity was resumed, it will be paused and new intent
+ * will be delivered, followed by {@link #onResume}. If the activity wasn't in the resumed
+ * state, then new intent can be delivered immediately, with {@link #onResume()} called
+ * sometime later when activity becomes active again.
+ *
+ * <p>Note that {@link #getIntent} still returns the original Intent. You
+ * can use {@link #setIntent} to update it to this new Intent.
+ *
+ * @param intent The new intent that was started for the activity.
+ *
+ * @see #getIntent
+ * @see #setIntent
+ * @see #onResume
+ */
+ protected void onNewIntent(Intent intent) {
+ }
+
+ /**
+ * The hook for {@link ActivityThread} to save the state of this activity.
+ *
+ * Calls {@link #onSaveInstanceState(android.os.Bundle)}
+ * and {@link #saveManagedDialogs(android.os.Bundle)}.
+ *
+ * @param outState The bundle to save the state to.
+ */
+ final void performSaveInstanceState(@NonNull Bundle outState) {
+ dispatchActivityPreSaveInstanceState(outState);
+ onSaveInstanceState(outState);
+ saveManagedDialogs(outState);
+ mActivityTransitionState.saveState(outState);
+ storeHasCurrentPermissionRequest(outState);
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
+ dispatchActivityPostSaveInstanceState(outState);
+ }
+
+ /**
+ * The hook for {@link ActivityThread} to save the state of this activity.
+ *
+ * Calls {@link #onSaveInstanceState(android.os.Bundle)}
+ * and {@link #saveManagedDialogs(android.os.Bundle)}.
+ *
+ * @param outState The bundle to save the state to.
+ * @param outPersistentState The bundle to save persistent state to.
+ */
+ final void performSaveInstanceState(@NonNull Bundle outState,
+ @NonNull PersistableBundle outPersistentState) {
+ dispatchActivityPreSaveInstanceState(outState);
+ onSaveInstanceState(outState, outPersistentState);
+ saveManagedDialogs(outState);
+ storeHasCurrentPermissionRequest(outState);
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
+ ", " + outPersistentState);
+ dispatchActivityPostSaveInstanceState(outState);
+ }
+
+ /**
+ * Called to retrieve per-instance state from an activity before being killed
+ * so that the state can be restored in {@link #onCreate} or
+ * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
+ * will be passed to both).
+ *
+ * <p>This method is called before an activity may be killed so that when it
+ * comes back some time in the future it can restore its state. For example,
+ * if activity B is launched in front of activity A, and at some point activity
+ * A is killed to reclaim resources, activity A will have a chance to save the
+ * current state of its user interface via this method so that when the user
+ * returns to activity A, the state of the user interface can be restored
+ * via {@link #onCreate} or {@link #onRestoreInstanceState}.
+ *
+ * <p>Do not confuse this method with activity lifecycle callbacks such as {@link #onPause},
+ * which is always called when the user no longer actively interacts with an activity, or
+ * {@link #onStop} which is called when activity becomes invisible. One example of when
+ * {@link #onPause} and {@link #onStop} is called and not this method is when a user navigates
+ * back from activity B to activity A: there is no need to call {@link #onSaveInstanceState}
+ * on B because that particular instance will never be restored,
+ * so the system avoids calling it. An example when {@link #onPause} is called and
+ * not {@link #onSaveInstanceState} is when activity B is launched in front of activity A:
+ * the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't
+ * killed during the lifetime of B since the state of the user interface of
+ * A will stay intact.
+ *
+ * <p>The default implementation takes care of most of the UI per-instance
+ * state for you by calling {@link android.view.View#onSaveInstanceState()} on each
+ * view in the hierarchy that has an id, and by saving the id of the currently
+ * focused view (all of which is restored by the default implementation of
+ * {@link #onRestoreInstanceState}). If you override this method to save additional
+ * information not captured by each individual view, you will likely want to
+ * call through to the default implementation, otherwise be prepared to save
+ * all of the state of each view yourself.
+ *
+ * <p>If called, this method will occur after {@link #onStop} for applications
+ * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
+ * For applications targeting earlier platform versions this method will occur
+ * before {@link #onStop} and there are no guarantees about whether it will
+ * occur before or after {@link #onPause}.
+ *
+ * @param outState Bundle in which to place your saved state.
+ *
+ * @see #onCreate
+ * @see #onRestoreInstanceState
+ * @see #onPause
+ */
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
+
+ Parcelable p = mFragments.saveAllState();
+ if (p != null) {
+ outState.putParcelable(FRAGMENTS_TAG, p);
+ }
+ getAutofillClientController().onSaveInstanceState(outState);
+ dispatchActivitySaveInstanceState(outState);
+ }
+
+ /**
+ * This is the same as {@link #onSaveInstanceState} but is called for activities
+ * created with the attribute {@link android.R.attr#persistableMode} set to
+ * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed
+ * in will be saved and presented in {@link #onCreate(Bundle, PersistableBundle)}
+ * the first time that this activity is restarted following the next device reboot.
+ *
+ * @param outState Bundle in which to place your saved state.
+ * @param outPersistentState State which will be saved across reboots.
+ *
+ * @see #onSaveInstanceState(Bundle)
+ * @see #onCreate
+ * @see #onRestoreInstanceState(Bundle, PersistableBundle)
+ * @see #onPause
+ */
+ public void onSaveInstanceState(@NonNull Bundle outState,
+ @NonNull PersistableBundle outPersistentState) {
+ onSaveInstanceState(outState);
+ }
+
+ /**
+ * Save the state of any managed dialogs.
+ *
+ * @param outState place to store the saved state.
+ */
+ @UnsupportedAppUsage
+ private void saveManagedDialogs(Bundle outState) {
+ if (mManagedDialogs == null) {
+ return;
+ }
+
+ final int numDialogs = mManagedDialogs.size();
+ if (numDialogs == 0) {
+ return;
+ }
+
+ Bundle dialogState = new Bundle();
+
+ int[] ids = new int[mManagedDialogs.size()];
+
+ // save each dialog's bundle, gather the ids
+ for (int i = 0; i < numDialogs; i++) {
+ final int key = mManagedDialogs.keyAt(i);
+ ids[i] = key;
+ final ManagedDialog md = mManagedDialogs.valueAt(i);
+ dialogState.putBundle(savedDialogKeyFor(key), md.mDialog.onSaveInstanceState());
+ if (md.mArgs != null) {
+ dialogState.putBundle(savedDialogArgsKeyFor(key), md.mArgs);
+ }
+ }
+
+ dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids);
+ outState.putBundle(SAVED_DIALOGS_TAG, dialogState);
+ }
+
+
+ /**
+ * Called as part of the activity lifecycle when the user no longer actively interacts with the
+ * activity, but it is still visible on screen. The counterpart to {@link #onResume}.
+ *
+ * <p>When activity B is launched in front of activity A, this callback will
+ * be invoked on A. B will not be created until A's {@link #onPause} returns,
+ * so be sure to not do anything lengthy here.
+ *
+ * <p>This callback is mostly used for saving any persistent state the
+ * activity is editing, to present a "edit in place" model to the user and
+ * making sure nothing is lost if there are not enough resources to start
+ * the new activity without first killing this one. This is also a good
+ * place to stop things that consume a noticeable amount of CPU in order to
+ * make the switch to the next activity as fast as possible.
+ *
+ * <p>On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good
+ * place to try to close exclusive-access devices or to release access to singleton resources.
+ * Starting with {@link android.os.Build.VERSION_CODES#Q} there can be multiple resumed
+ * activities in the system at the same time, so {@link #onTopResumedActivityChanged(boolean)}
+ * should be used for that purpose instead.
+ *
+ * <p>If an activity is launched on top, after receiving this call you will usually receive a
+ * following call to {@link #onStop} (after the next activity has been resumed and displayed
+ * above). However in some cases there will be a direct call back to {@link #onResume} without
+ * going through the stopped state. An activity can also rest in paused state in some cases when
+ * in multi-window mode, still visible to user.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onResume
+ * @see #onSaveInstanceState
+ * @see #onStop
+ */
+ @CallSuper
+ protected void onPause() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
+ dispatchActivityPaused();
+ getAutofillClientController().onActivityPaused();
+
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE);
+
+ mCalled = true;
+ }
+
+ /**
+ * Called as part of the activity lifecycle when an activity is about to go
+ * into the background as the result of user choice. For example, when the
+ * user presses the Home key, {@link #onUserLeaveHint} will be called, but
+ * when an incoming phone call causes the in-call Activity to be automatically
+ * brought to the foreground, {@link #onUserLeaveHint} will not be called on
+ * the activity being interrupted. In cases when it is invoked, this method
+ * is called right before the activity's {@link #onPause} callback.
+ *
+ * <p>This callback and {@link #onUserInteraction} are intended to help
+ * activities manage status bar notifications intelligently; specifically,
+ * for helping activities determine the proper time to cancel a notification.
+ *
+ * @see #onUserInteraction()
+ * @see android.content.Intent#FLAG_ACTIVITY_NO_USER_ACTION
+ */
+ protected void onUserLeaveHint() {
+ }
+
+ /**
+ * @deprecated Method doesn't do anything and will be removed in the future.
+ */
+ @Deprecated
+ public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
+ return false;
+ }
+
+ /**
+ * Generate a new description for this activity. This method is called
+ * before stopping the activity and can, if desired, return some textual
+ * description of its current state to be displayed to the user.
+ *
+ * <p>The default implementation returns null, which will cause you to
+ * inherit the description from the previous activity. If all activities
+ * return null, generally the label of the top activity will be used as the
+ * description.
+ *
+ * @return A description of what the user is doing. It should be short and
+ * sweet (only a few words).
+ *
+ * @see #onSaveInstanceState
+ * @see #onStop
+ */
+ @Nullable
+ public CharSequence onCreateDescription() {
+ return null;
+ }
+
+ /**
+ * This is called when the user is requesting an assist, to build a full
+ * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+ * application. You can override this method to place into the bundle anything
+ * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+ * of the assist Intent.
+ *
+ * <p>This function will be called after any global assist callbacks that had
+ * been registered with {@link Application#registerOnProvideAssistDataListener
+ * Application.registerOnProvideAssistDataListener}.
+ */
+ public void onProvideAssistData(Bundle data) {
+ }
+
+ /**
+ * This is called when the user is requesting an assist, to provide references
+ * to content related to the current activity. Before being called, the
+ * {@code outContent} Intent is filled with the base Intent of the activity (the Intent
+ * returned by {@link #getIntent()}). The Intent's extras are stripped of any types
+ * that are not valid for {@link PersistableBundle} or non-framework Parcelables, and
+ * the flags {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} and
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} are cleared from the Intent.
+ *
+ * <p>Custom implementation may adjust the content intent to better reflect the top-level
+ * context of the activity, and fill in its ClipData with additional content of
+ * interest that the user is currently viewing. For example, an image gallery application
+ * that has launched in to an activity allowing the user to swipe through pictures should
+ * modify the intent to reference the current image they are looking it; such an
+ * application when showing a list of pictures should add a ClipData that has
+ * references to all of the pictures currently visible on screen.</p>
+ *
+ * @param outContent The assist content to return.
+ */
+ public void onProvideAssistContent(AssistContent outContent) {
+ }
+
+ /**
+ * Returns the list of direct actions supported by the app.
+ *
+ * <p>You should return the list of actions that could be executed in the
+ * current context, which is in the current state of the app. If the actions
+ * that could be executed by the app changes you should report that via
+ * calling {@link VoiceInteractor#notifyDirectActionsChanged()}.
+ *
+ * <p>To get the voice interactor you need to call {@link #getVoiceInteractor()}
+ * which would return non <code>null</code> only if there is an ongoing voice
+ * interaction session. You can also detect when the voice interactor is no
+ * longer valid because the voice interaction session that is backing is finished
+ * by calling {@link VoiceInteractor#registerOnDestroyedCallback(Executor, Runnable)}.
+ *
+ * <p>This method will be called only after {@link #onStart()} and before {@link #onStop()}.
+ *
+ * <p>You should pass to the callback the currently supported direct actions which
+ * cannot be <code>null</code> or contain <code>null</code> elements.
+ *
+ * <p>You should return the action list as soon as possible to ensure the consumer,
+ * for example the assistant, is as responsive as possible which would improve user
+ * experience of your app.
+ *
+ * @param cancellationSignal A signal to cancel the operation in progress.
+ * @param callback The callback to send the action list. The actions list cannot
+ * contain <code>null</code> elements. You can call this on any thread.
+ */
+ public void onGetDirectActions(@NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<List<DirectAction>> callback) {
+ callback.accept(Collections.emptyList());
+ }
+
+ /**
+ * This is called to perform an action previously defined by the app.
+ * Apps also have access to {@link #getVoiceInteractor()} to follow up on the action.
+ *
+ * @param actionId The ID for the action you previously reported via
+ * {@link #onGetDirectActions(CancellationSignal, Consumer)}.
+ * @param arguments Any additional arguments provided by the caller that are
+ * specific to the given action.
+ * @param cancellationSignal A signal to cancel the operation in progress.
+ * @param resultListener The callback to provide the result back to the caller.
+ * You can call this on any thread. The result bundle is action specific.
+ *
+ * @see #onGetDirectActions(CancellationSignal, Consumer)
+ */
+ public void onPerformDirectAction(@NonNull String actionId,
+ @NonNull Bundle arguments, @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<Bundle> resultListener) { }
+
+ /**
+ * Request the Keyboard Shortcuts screen to show up. This will trigger
+ * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
+ */
+ public final void requestShowKeyboardShortcuts() {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
+ Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+ intent.setPackage(sysuiComponent.getPackageName());
+ sendBroadcastAsUser(intent, Process.myUserHandle());
+ }
+
+ /**
+ * Dismiss the Keyboard Shortcuts screen.
+ */
+ public final void dismissKeyboardShortcutsHelper() {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
+ Intent intent = new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS);
+ intent.setPackage(sysuiComponent.getPackageName());
+ sendBroadcastAsUser(intent, Process.myUserHandle());
+ }
+
+ @Override
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+ if (menu == null) {
+ return;
+ }
+ KeyboardShortcutGroup group = null;
+ int menuSize = menu.size();
+ for (int i = 0; i < menuSize; ++i) {
+ final MenuItem item = menu.getItem(i);
+ final CharSequence title = item.getTitle();
+ final char alphaShortcut = item.getAlphabeticShortcut();
+ final int alphaModifiers = item.getAlphabeticModifiers();
+ if (title != null && alphaShortcut != MIN_VALUE) {
+ if (group == null) {
+ final int resource = mApplication.getApplicationInfo().labelRes;
+ group = new KeyboardShortcutGroup(resource != 0 ? getString(resource) : null);
+ }
+ group.addItem(new KeyboardShortcutInfo(
+ title, alphaShortcut, alphaModifiers));
+ }
+ }
+ if (group != null) {
+ data.add(group);
+ }
+ }
+
+ /**
+ * Ask to have the current assistant shown to the user. This only works if the calling
+ * activity is the current foreground activity. It is the same as calling
+ * {@link android.service.voice.VoiceInteractionService#showSession
+ * VoiceInteractionService.showSession} and requesting all of the possible context.
+ * The receiver will always see
+ * {@link android.service.voice.VoiceInteractionSession#SHOW_SOURCE_APPLICATION} set.
+ * @return Returns true if the assistant was successfully invoked, else false. For example
+ * false will be returned if the caller is not the current top activity.
+ */
+ public boolean showAssist(Bundle args) {
+ return ActivityClient.getInstance().showAssistFromActivity(mToken, args);
+ }
+
+ /**
+ * Called when you are no longer visible to the user. You will next
+ * receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
+ * depending on later user activity. This is a good place to stop
+ * refreshing UI, running animations and other visual things.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onRestart
+ * @see #onResume
+ * @see #onSaveInstanceState
+ * @see #onDestroy
+ */
+ @CallSuper
+ protected void onStop() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
+ if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
+ mActivityTransitionState.onStop(this);
+ dispatchActivityStopped();
+ mTranslucentCallback = null;
+ mCalled = true;
+
+ getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
+ mEnterAnimationComplete = false;
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
+ }
+
+ /**
+ * Perform any final cleanup before an activity is destroyed. This can
+ * happen either because the activity is finishing (someone called
+ * {@link #finish} on it), or because the system is temporarily destroying
+ * this instance of the activity to save space. You can distinguish
+ * between these two scenarios with the {@link #isFinishing} method.
+ *
+ * <p><em>Note: do not count on this method being called as a place for
+ * saving data! For example, if an activity is editing data in a content
+ * provider, those edits should be committed in either {@link #onPause} or
+ * {@link #onSaveInstanceState}, not here.</em> This method is usually implemented to
+ * free resources like threads that are associated with an activity, so
+ * that a destroyed activity does not leave such things around while the
+ * rest of its application is still running. There are situations where
+ * the system will simply kill the activity's hosting process without
+ * calling this method (or any others) in it, so it should not be used to
+ * do things that are intended to remain around after the process goes
+ * away.
+ *
+ * <p><em>Derived classes must call through to the super class's
+ * implementation of this method. If they do not, an exception will be
+ * thrown.</em></p>
+ *
+ * @see #onPause
+ * @see #onStop
+ * @see #finish
+ * @see #isFinishing
+ */
+ @CallSuper
+ protected void onDestroy() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
+ mCalled = true;
+
+ getAutofillClientController().onActivityDestroyed();
+
+ // dismiss any dialogs we are managing.
+ if (mManagedDialogs != null) {
+ final int numDialogs = mManagedDialogs.size();
+ for (int i = 0; i < numDialogs; i++) {
+ final ManagedDialog md = mManagedDialogs.valueAt(i);
+ if (md.mDialog.isShowing()) {
+ md.mDialog.dismiss();
+ }
+ }
+ mManagedDialogs = null;
+ }
+
+ // close any cursors we are managing.
+ synchronized (mManagedCursors) {
+ int numCursors = mManagedCursors.size();
+ for (int i = 0; i < numCursors; i++) {
+ ManagedCursor c = mManagedCursors.get(i);
+ if (c != null) {
+ c.mCursor.close();
+ }
+ }
+ mManagedCursors.clear();
+ }
+
+ // Close any open search dialog
+ if (mSearchManager != null) {
+ mSearchManager.stopSearch();
+ }
+
+ if (mActionBar != null) {
+ mActionBar.onDestroy();
+ }
+
+ dispatchActivityDestroyed();
+
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
+
+ if (mUiTranslationController != null) {
+ mUiTranslationController.onActivityDestroyed();
+ }
+ if (mDefaultBackCallback != null) {
+ getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+ mDefaultBackCallback = null;
+ }
+
+ if (mCallbacksController != null) {
+ mCallbacksController.clearCallbacks();
+ }
+ }
+
+ /**
+ * Report to the system that your app is now fully drawn, for diagnostic and
+ * optimization purposes. The system may adjust optimizations to prioritize
+ * work that happens before reportFullyDrawn is called, to improve app startup.
+ * Misrepresenting the startup window by calling reportFullyDrawn too late or too
+ * early may decrease application and startup performance.<p>
+ * This is also used to help instrument application launch times, so that the
+ * app can report when it is fully in a usable state; without this, the only thing
+ * the system itself can determine is the point at which the activity's window
+ * is <em>first</em> drawn and displayed. To participate in app launch time
+ * measurement, you should always call this method after first launch (when
+ * {@link #onCreate(android.os.Bundle)} is called), at the point where you have
+ * entirely drawn your UI and populated with all of the significant data. You
+ * can safely call this method any time after first launch as well, in which case
+ * it will simply be ignored.
+ * <p>If this method is called before the activity's window is <em>first</em> drawn
+ * and displayed as measured by the system, the reported time here will be shifted
+ * to the system measured time.
+ */
+ public void reportFullyDrawn() {
+ if (mDoReportFullyDrawn) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "reportFullyDrawn() for " + mComponent.toShortString());
+ }
+ mDoReportFullyDrawn = false;
+ try {
+ ActivityClient.getInstance().reportActivityFullyDrawn(
+ mToken, mRestoredFromBundle);
+ VMRuntime.getRuntime().notifyStartupCompleted();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+ }
+
+ /**
+ * Called by the system when the activity changes from fullscreen mode to multi-window mode and
+ * visa-versa. This method provides the same configuration that will be sent in the following
+ * {@link #onConfigurationChanged(Configuration)} call after the activity enters this mode.
+ *
+ * @see android.R.attr#resizeableActivity
+ *
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
+ * @param newConfig The new configuration of the activity with the state
+ * {@param isInMultiWindowMode}.
+ */
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+ // Left deliberately empty. There should be no side effects if a direct
+ // subclass of Activity does not call super.
+ onMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ /**
+ * Called by the system when the activity changes from fullscreen mode to multi-window mode and
+ * visa-versa.
+ *
+ * @see android.R.attr#resizeableActivity
+ *
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
+ *
+ * @deprecated Use {@link #onMultiWindowModeChanged(boolean, Configuration)} instead.
+ */
+ @Deprecated
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ // Left deliberately empty. There should be no side effects if a direct
+ // subclass of Activity does not call super.
+ }
+
+ /**
+ * Returns true if the activity is currently in multi-window mode.
+ * @see android.R.attr#resizeableActivity
+ *
+ * @return True if the activity is in multi-window mode.
+ */
+ public boolean isInMultiWindowMode() {
+ return mIsInMultiWindowMode;
+ }
+
+ /**
+ * Called by the system when the activity changes to and from picture-in-picture mode. This
+ * method provides the same configuration that will be sent in the following
+ * {@link #onConfigurationChanged(Configuration)} call after the activity enters this mode.
+ *
+ * @see android.R.attr#supportsPictureInPicture
+ *
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+ * @param newConfig The new configuration of the activity with the state
+ * {@param isInPictureInPictureMode}.
+ */
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+ Configuration newConfig) {
+ // Left deliberately empty. There should be no side effects if a direct
+ // subclass of Activity does not call super.
+ onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ /**
+ * Called by the system when the activity is in PiP and has state changes.
+ *
+ * Compare to {@link #onPictureInPictureModeChanged(boolean, Configuration)}, which is only
+ * called when PiP mode changes (meaning, enters or exits PiP), this can be called at any time
+ * while the activity is in PiP mode. Therefore, all invocation can only happen after
+ * {@link #onPictureInPictureModeChanged(boolean, Configuration)} is called with true, and
+ * before {@link #onPictureInPictureModeChanged(boolean, Configuration)} is called with false.
+ * You would not need to worry about cases where this is called and the activity is not in
+ * Picture-In-Picture mode. For managing cases where the activity enters/exits
+ * Picture-in-Picture (e.g. resources clean-up on exit), use
+ * {@link #onPictureInPictureModeChanged(boolean, Configuration)}.
+ *
+ * The default state is everything declared in {@link PictureInPictureUiState} is false, such as
+ * {@link PictureInPictureUiState#isStashed()}.
+ *
+ * @param pipState the new Picture-in-Picture state.
+ */
+ public void onPictureInPictureUiStateChanged(@NonNull PictureInPictureUiState pipState) {
+ // Left deliberately empty. There should be no side effects if a direct
+ // subclass of Activity does not call super.
+ }
+
+ /**
+ * Called by the system when the activity changes to and from picture-in-picture mode.
+ *
+ * @see android.R.attr#supportsPictureInPicture
+ *
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+ *
+ * @deprecated Use {@link #onPictureInPictureModeChanged(boolean, Configuration)} instead.
+ */
+ @Deprecated
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ // Left deliberately empty. There should be no side effects if a direct
+ // subclass of Activity does not call super.
+ }
+
+ /**
+ * Returns true if the activity is currently in picture-in-picture mode.
+ * @see android.R.attr#supportsPictureInPicture
+ *
+ * @return True if the activity is in picture-in-picture mode.
+ */
+ public boolean isInPictureInPictureMode() {
+ return mIsInPictureInPictureMode;
+ }
+
+ /**
+ * Puts the activity in picture-in-picture mode if possible in the current system state. Any
+ * prior calls to {@link #setPictureInPictureParams(PictureInPictureParams)} will still apply
+ * when entering picture-in-picture through this call.
+ *
+ * @see #enterPictureInPictureMode(PictureInPictureParams)
+ * @see android.R.attr#supportsPictureInPicture
+ */
+ @Deprecated
+ public void enterPictureInPictureMode() {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
+
+ /**
+ * Puts the activity in picture-in-picture mode if possible in the current system state. The
+ * set parameters in {@param params} will be combined with the parameters from prior calls to
+ * {@link #setPictureInPictureParams(PictureInPictureParams)}.
+ *
+ * The system may disallow entering picture-in-picture in various cases, including when the
+ * activity is not visible, if the screen is locked or if the user has an activity pinned.
+ *
+ * <p>By default, system calculates the dimension of picture-in-picture window based on the
+ * given {@param params}.
+ * See <a href="{@docRoot}guide/topics/ui/picture-in-picture">Picture-in-picture Support</a>
+ * on how to override this behavior.</p>
+ *
+ * @see android.R.attr#supportsPictureInPicture
+ * @see PictureInPictureParams
+ *
+ * @param params non-null parameters to be combined with previously set parameters when entering
+ * picture-in-picture.
+ *
+ * @return true if the system successfully put this activity into picture-in-picture mode or was
+ * already in picture-in-picture mode (see {@link #isInPictureInPictureMode()}). If the device
+ * does not support picture-in-picture, return false.
+ */
+ public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) {
+ if (!deviceSupportsPictureInPictureMode()) {
+ return false;
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("Expected non-null picture-in-picture params");
+ }
+ if (!mCanEnterPictureInPicture) {
+ throw new IllegalStateException("Activity must be resumed to enter"
+ + " picture-in-picture");
+ }
+ // Set mIsInPictureInPictureMode earlier and don't wait for
+ // onPictureInPictureModeChanged callback here. This is to ensure that
+ // isInPictureInPictureMode returns true in the following onPause callback.
+ // See https://developer.android.com/guide/topics/ui/picture-in-picture for guidance.
+ mIsInPictureInPictureMode = ActivityClient.getInstance().enterPictureInPictureMode(
+ mToken, params);
+ return mIsInPictureInPictureMode;
+ }
+
+ /**
+ * Updates the properties of the picture-in-picture activity, or sets it to be used later when
+ * {@link #enterPictureInPictureMode()} is called.
+ *
+ * @param params the new parameters for the picture-in-picture.
+ */
+ public void setPictureInPictureParams(@NonNull PictureInPictureParams params) {
+ if (!deviceSupportsPictureInPictureMode()) {
+ return;
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("Expected non-null picture-in-picture params");
+ }
+ ActivityClient.getInstance().setPictureInPictureParams(mToken, params);
+ }
+
+ /**
+ * Return the number of actions that will be displayed in the picture-in-picture UI when the
+ * user interacts with the activity currently in picture-in-picture mode. This number may change
+ * if the global configuration changes (ie. if the device is plugged into an external display),
+ * but will always be at least three.
+ */
+ public int getMaxNumPictureInPictureActions() {
+ return ActivityTaskManager.getMaxNumPictureInPictureActions(this);
+ }
+
+ /**
+ * @return Whether this device supports picture-in-picture.
+ */
+ private boolean deviceSupportsPictureInPictureMode() {
+ return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
+ }
+
+ /**
+ * This method is called by the system in various cases where picture in picture mode should be
+ * entered if supported.
+ *
+ * <p>It is up to the app developer to choose whether to call
+ * {@link #enterPictureInPictureMode(PictureInPictureParams)} at this time. For example, the
+ * system will call this method when the activity is being put into the background, so the app
+ * developer might want to switch an activity into PIP mode instead.</p>
+ *
+ * @return {@code true} if the activity received this callback regardless of if it acts on it
+ * or not. If {@code false}, the framework will assume the app hasn't been updated to leverage
+ * this callback and will in turn send a legacy callback of {@link #onUserLeaveHint()} for the
+ * app to enter picture-in-picture mode.
+ */
+ public boolean onPictureInPictureRequested() {
+ return false;
+ }
+
+ /**
+ * Request to put the a freeform activity into fullscreen. This will only be allowed if the
+ * activity is on a freeform display, such as a desktop device. The requester has to be the
+ * top-most activity of the focused display, and the request should be a response to a user
+ * input. When getting fullscreen and receiving corresponding
+ * {@link #onConfigurationChanged(Configuration)} and
+ * {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
+ * itself and the system bars' visibilities can be controlled as usual fullscreen apps.
+ *
+ * Calling it again with the exit request can restore the activity to the previous status.
+ * This will only happen when it got into fullscreen through this API.
+ *
+ * If an app wants to be in fullscreen always, it should claim as not being resizable
+ * by setting
+ * <a href="https://developer.android.com/guide/topics/large-screens/multi-window-support#resizeableActivity">
+ * {@code android:resizableActivity="false"}</a> instead of calling this API.
+ *
+ * @param request Can be {@link #FULLSCREEN_MODE_REQUEST_ENTER} or
+ * {@link #FULLSCREEN_MODE_REQUEST_EXIT} to indicate this request is to get
+ * fullscreen or get restored.
+ * @param approvalCallback Optional callback, use {@code null} when not necessary. When the
+ * request is approved or rejected, the callback will be triggered. This
+ * will happen before any configuration change. The callback will be
+ * dispatched on the main thread. If the request is rejected, the
+ * Throwable provided will be an {@link IllegalStateException} with a
+ * detailed message can be retrieved by {@link Throwable#getMessage()}.
+ */
+ public void requestFullscreenMode(@FullscreenModeRequest int request,
+ @Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
+ FullscreenRequestHandler.requestFullscreenMode(
+ request, approvalCallback, mCurrentConfig, getActivityToken());
+ }
+
+ /**
+ * Specifies a preference to dock big overlays like the expanded picture-in-picture on TV
+ * (see {@link PictureInPictureParams.Builder#setExpandedAspectRatio}). Docking puts the
+ * big overlay side-by-side next to this activity, so that both windows are fully visible to
+ * the user.
+ *
+ * <p> If unspecified, whether the overlay window will be docked or not, will be defined
+ * by the system.
+ *
+ * <p> If specified, the system will try to respect the preference, but it may be
+ * overridden by a user preference.
+ *
+ * @param shouldDockBigOverlays indicates that big overlays should be docked next to the
+ * activity instead of overlay its content
+ *
+ * @see PictureInPictureParams.Builder#setExpandedAspectRatio
+ * @see #shouldDockBigOverlays
+ */
+ public void setShouldDockBigOverlays(boolean shouldDockBigOverlays) {
+ ActivityClient.getInstance().setShouldDockBigOverlays(mToken, shouldDockBigOverlays);
+ mShouldDockBigOverlays = shouldDockBigOverlays;
+ }
+
+ /**
+ * Returns whether big overlays should be docked next to the activity as set by
+ * {@link #setShouldDockBigOverlays}.
+ *
+ * @return {@code true} if big overlays should be docked next to the activity instead
+ * of overlay its content
+ *
+ * @see #setShouldDockBigOverlays
+ */
+ public boolean shouldDockBigOverlays() {
+ return mShouldDockBigOverlays;
+ }
+
+ void dispatchMovedToDisplay(int displayId, Configuration config) {
+ updateDisplay(displayId);
+ onMovedToDisplay(displayId, config);
+ }
+
+ /**
+ * Called by the system when the activity is moved from one display to another without
+ * recreation. This means that this activity is declared to handle all changes to configuration
+ * that happened when it was switched to another display, so it wasn't destroyed and created
+ * again.
+ *
+ * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+ * applied configuration actually changed. It is up to app developer to choose whether to handle
+ * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+ * call.
+ *
+ * <p>Use this callback to track changes to the displays if some activity functionality relies
+ * on an association with some display properties.
+ *
+ * @param displayId The id of the display to which activity was moved.
+ * @param config Configuration of the activity resources on new display after move.
+ *
+ * @see #onConfigurationChanged(Configuration)
+ * @see View#onMovedToDisplay(int, Configuration)
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public void onMovedToDisplay(int displayId, Configuration config) {
+ }
+
+ /**
+ * Called by the system when the device configuration changes while your
+ * activity is running. Note that this will <em>only</em> be called if
+ * you have selected configurations you would like to handle with the
+ * {@link android.R.attr#configChanges} attribute in your manifest. If
+ * any configuration change occurs that is not selected to be reported
+ * by that attribute, then instead of reporting it the system will stop
+ * and restart the activity (to have it launched with the new
+ * configuration).
+ *
+ * <p>At the time that this function has been called, your Resources
+ * object will have been updated to return resource values matching the
+ * new configuration.
+ *
+ * @param newConfig The new device configuration.
+ */
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
+ mCalled = true;
+
+ mFragments.dispatchConfigurationChanged(newConfig);
+
+ if (mWindow != null) {
+ // Pass the configuration changed event to the window
+ mWindow.onConfigurationChanged(newConfig);
+ }
+
+ if (mActionBar != null) {
+ // Do this last; the action bar will need to access
+ // view changes from above.
+ mActionBar.onConfigurationChanged(newConfig);
+ }
+
+ dispatchActivityConfigurationChanged();
+ if (mCallbacksController != null) {
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
+ }
+ }
+
+ /**
+ * If this activity is being destroyed because it can not handle a
+ * configuration parameter being changed (and thus its
+ * {@link #onConfigurationChanged(Configuration)} method is
+ * <em>not</em> being called), then you can use this method to discover
+ * the set of changes that have occurred while in the process of being
+ * destroyed. Note that there is no guarantee that these will be
+ * accurate (other changes could have happened at any time), so you should
+ * only use this as an optimization hint.
+ *
+ * @return Returns a bit field of the configuration parameters that are
+ * changing, as defined by the {@link android.content.res.Configuration}
+ * class.
+ */
+ public int getChangingConfigurations() {
+ return mConfigChangeFlags;
+ }
+
+ /**
+ * Retrieve the non-configuration instance data that was previously
+ * returned by {@link #onRetainNonConfigurationInstance()}. This will
+ * be available from the initial {@link #onCreate} and
+ * {@link #onStart} calls to the new instance, allowing you to extract
+ * any useful dynamic state from the previous instance.
+ *
+ * <p>Note that the data you retrieve here should <em>only</em> be used
+ * as an optimization for handling configuration changes. You should always
+ * be able to handle getting a null pointer back, and an activity must
+ * still be able to restore itself to its previous state (through the
+ * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+ * function returns null.
+ *
+ * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
+ * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+ * available on older platforms through the Android support libraries.
+ *
+ * @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
+ */
+ @Nullable
+ public Object getLastNonConfigurationInstance() {
+ return mLastNonConfigurationInstances != null
+ ? mLastNonConfigurationInstances.activity : null;
+ }
+
+ /**
+ * Called by the system, as part of destroying an
+ * activity due to a configuration change, when it is known that a new
+ * instance will immediately be created for the new configuration. You
+ * can return any object you like here, including the activity instance
+ * itself, which can later be retrieved by calling
+ * {@link #getLastNonConfigurationInstance()} in the new activity
+ * instance.
+ *
+ * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * or later, consider instead using a {@link Fragment} with
+ * {@link Fragment#setRetainInstance(boolean)
+ * Fragment.setRetainInstance(boolean}.</em>
+ *
+ * <p>This function is called purely as an optimization, and you must
+ * not rely on it being called. When it is called, a number of guarantees
+ * will be made to help optimize configuration switching:
+ * <ul>
+ * <li> The function will be called between {@link #onStop} and
+ * {@link #onDestroy}.
+ * <li> A new instance of the activity will <em>always</em> be immediately
+ * created after this one's {@link #onDestroy()} is called. In particular,
+ * <em>no</em> messages will be dispatched during this time (when the returned
+ * object does not have an activity to be associated with).
+ * <li> The object you return here will <em>always</em> be available from
+ * the {@link #getLastNonConfigurationInstance()} method of the following
+ * activity instance as described there.
+ * </ul>
+ *
+ * <p>These guarantees are designed so that an activity can use this API
+ * to propagate extensive state from the old to new activity instance, from
+ * loaded bitmaps, to network connections, to evenly actively running
+ * threads. Note that you should <em>not</em> propagate any data that
+ * may change based on the configuration, including any data loaded from
+ * resources such as strings, layouts, or drawables.
+ *
+ * <p>The guarantee of no message handling during the switch to the next
+ * activity simplifies use with active objects. For example if your retained
+ * state is an {@link android.os.AsyncTask} you are guaranteed that its
+ * call back functions (like {@link android.os.AsyncTask#onPostExecute}) will
+ * not be called from the call here until you execute the next instance's
+ * {@link #onCreate(Bundle)}. (Note however that there is of course no such
+ * guarantee for {@link android.os.AsyncTask#doInBackground} since that is
+ * running in a separate thread.)
+ *
+ * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
+ * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+ * available on older platforms through the Android support libraries.
+ *
+ * @return any Object holding the desired state to propagate to the
+ * next activity instance
+ */
+ public Object onRetainNonConfigurationInstance() {
+ return null;
+ }
+
+ /**
+ * Retrieve the non-configuration instance data that was previously
+ * returned by {@link #onRetainNonConfigurationChildInstances()}. This will
+ * be available from the initial {@link #onCreate} and
+ * {@link #onStart} calls to the new instance, allowing you to extract
+ * any useful dynamic state from the previous instance.
+ *
+ * <p>Note that the data you retrieve here should <em>only</em> be used
+ * as an optimization for handling configuration changes. You should always
+ * be able to handle getting a null pointer back, and an activity must
+ * still be able to restore itself to its previous state (through the
+ * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+ * function returns null.
+ *
+ * @return Returns the object previously returned by
+ * {@link #onRetainNonConfigurationChildInstances()}
+ */
+ @Nullable
+ HashMap<String, Object> getLastNonConfigurationChildInstances() {
+ return mLastNonConfigurationInstances != null
+ ? mLastNonConfigurationInstances.children : null;
+ }
+
+ /**
+ * This method is similar to {@link #onRetainNonConfigurationInstance()} except that
+ * it should return either a mapping from child activity id strings to arbitrary objects,
+ * or null. This method is intended to be used by Activity framework subclasses that control a
+ * set of child activities, such as ActivityGroup. The same guarantees and restrictions apply
+ * as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null.
+ */
+ @Nullable
+ HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+ return null;
+ }
+
+ NonConfigurationInstances retainNonConfigurationInstances() {
+ Object activity = onRetainNonConfigurationInstance();
+ HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
+ FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
+
+ // We're already stopped but we've been asked to retain.
+ // Our fragments are taken care of but we need to mark the loaders for retention.
+ // In order to do this correctly we need to restart the loaders first before
+ // handing them off to the next activity.
+ mFragments.doLoaderStart();
+ mFragments.doLoaderStop(true);
+ ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
+
+ if (activity == null && children == null && fragments == null && loaders == null
+ && mVoiceInteractor == null) {
+ return null;
+ }
+
+ NonConfigurationInstances nci = new NonConfigurationInstances();
+ nci.activity = activity;
+ nci.children = children;
+ nci.fragments = fragments;
+ nci.loaders = loaders;
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.retainInstance();
+ nci.voiceInteractor = mVoiceInteractor;
+ }
+ return nci;
+ }
+
+ public void onLowMemory() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
+ mCalled = true;
+ mFragments.dispatchLowMemory();
+ if (mCallbacksController != null) {
+ mCallbacksController.dispatchLowMemory();
+ }
+ }
+
+ public void onTrimMemory(int level) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
+ mCalled = true;
+ mFragments.dispatchTrimMemory(level);
+ if (mCallbacksController != null) {
+ mCallbacksController.dispatchTrimMemory(level);
+ }
+ }
+
+ /**
+ * Return the FragmentManager for interacting with fragments associated
+ * with this activity.
+ *
+ * @deprecated Use {@link androidx.fragment.app.FragmentActivity#getSupportFragmentManager()}
+ */
+ @Deprecated
+ public FragmentManager getFragmentManager() {
+ return mFragments.getFragmentManager();
+ }
+
+ /**
+ * Called when a Fragment is being attached to this activity, immediately
+ * after the call to its {@link Fragment#onAttach Fragment.onAttach()}
+ * method and before {@link Fragment#onCreate Fragment.onCreate()}.
+ *
+ * @deprecated Use {@link
+ * androidx.fragment.app.FragmentActivity#onAttachFragment(androidx.fragment.app.Fragment)}
+ */
+ @Deprecated
+ public void onAttachFragment(Fragment fragment) {
+ }
+
+ /**
+ * Wrapper around
+ * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
+ * that gives the resulting {@link Cursor} to call
+ * {@link #startManagingCursor} so that the activity will manage its
+ * lifecycle for you.
+ *
+ * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * or later, consider instead using {@link LoaderManager} instead, available
+ * via {@link #getLoaderManager()}.</em>
+ *
+ * <p><strong>Warning:</strong> Do not call {@link Cursor#close()} on a cursor obtained using
+ * this method, because the activity will do that for you at the appropriate time. However, if
+ * you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
+ * not</em> automatically close the cursor and, in that case, you must call
+ * {@link Cursor#close()}.</p>
+ *
+ * @param uri The URI of the content provider to query.
+ * @param projection List of columns to return.
+ * @param selection SQL WHERE clause.
+ * @param sortOrder SQL ORDER BY clause.
+ *
+ * @return The Cursor that was returned by query().
+ *
+ * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
+ * @see #startManagingCursor
+ * @hide
+ *
+ * @deprecated Use {@link CursorLoader} instead.
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public final Cursor managedQuery(Uri uri, String[] projection, String selection,
+ String sortOrder) {
+ Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder);
+ if (c != null) {
+ startManagingCursor(c);
+ }
+ return c;
+ }
+
+ /**
+ * Wrapper around
+ * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
+ * that gives the resulting {@link Cursor} to call
+ * {@link #startManagingCursor} so that the activity will manage its
+ * lifecycle for you.
+ *
+ * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * or later, consider instead using {@link LoaderManager} instead, available
+ * via {@link #getLoaderManager()}.</em>
+ *
+ * <p><strong>Warning:</strong> Do not call {@link Cursor#close()} on a cursor obtained using
+ * this method, because the activity will do that for you at the appropriate time. However, if
+ * you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
+ * not</em> automatically close the cursor and, in that case, you must call
+ * {@link Cursor#close()}.</p>
+ *
+ * @param uri The URI of the content provider to query.
+ * @param projection List of columns to return.
+ * @param selection SQL WHERE clause.
+ * @param selectionArgs The arguments to selection, if any ?s are pesent
+ * @param sortOrder SQL ORDER BY clause.
+ *
+ * @return The Cursor that was returned by query().
+ *
+ * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
+ * @see #startManagingCursor
+ *
+ * @deprecated Use {@link CursorLoader} instead.
+ */
+ @Deprecated
+ public final Cursor managedQuery(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
+ if (c != null) {
+ startManagingCursor(c);
+ }
+ return c;
+ }
+
+ /**
+ * This method allows the activity to take care of managing the given
+ * {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
+ * That is, when the activity is stopped it will automatically call
+ * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted
+ * it will call {@link Cursor#requery} for you. When the activity is
+ * destroyed, all managed Cursors will be closed automatically.
+ *
+ * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * or later, consider instead using {@link LoaderManager} instead, available
+ * via {@link #getLoaderManager()}.</em>
+ *
+ * <p><strong>Warning:</strong> Do not call {@link Cursor#close()} on cursor obtained from
+ * {@link #managedQuery}, because the activity will do that for you at the appropriate time.
+ * However, if you call {@link #stopManagingCursor} on a cursor from a managed query, the system
+ * <em>will not</em> automatically close the cursor and, in that case, you must call
+ * {@link Cursor#close()}.</p>
+ *
+ * @param c The Cursor to be managed.
+ *
+ * @see #managedQuery(android.net.Uri , String[], String, String[], String)
+ * @see #stopManagingCursor
+ *
+ * @deprecated Use the new {@link android.content.CursorLoader} class with
+ * {@link LoaderManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ public void startManagingCursor(Cursor c) {
+ synchronized (mManagedCursors) {
+ mManagedCursors.add(new ManagedCursor(c));
+ }
+ }
+
+ /**
+ * Given a Cursor that was previously given to
+ * {@link #startManagingCursor}, stop the activity's management of that
+ * cursor.
+ *
+ * <p><strong>Warning:</strong> After calling this method on a cursor from a managed query,
+ * the system <em>will not</em> automatically close the cursor and you must call
+ * {@link Cursor#close()}.</p>
+ *
+ * @param c The Cursor that was being managed.
+ *
+ * @see #startManagingCursor
+ *
+ * @deprecated Use the new {@link android.content.CursorLoader} class with
+ * {@link LoaderManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ public void stopManagingCursor(Cursor c) {
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (mc.mCursor == c) {
+ mManagedCursors.remove(i);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}
+ * this is a no-op.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void setPersistent(boolean isPersistent) {
+ }
+
+ /**
+ * Finds a view that was identified by the {@code android:id} XML attribute
+ * that was processed in {@link #onCreate}.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID if found, or {@code null} otherwise
+ * @see View#findViewById(int)
+ * @see Activity#requireViewById(int)
+ */
+ @Nullable
+ public <T extends View> T findViewById(@IdRes int id) {
+ return getWindow().findViewById(id);
+ }
+
+ /**
+ * Finds a view that was identified by the {@code android:id} XML attribute that was processed
+ * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is
+ * no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Activity#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Activity");
+ }
+ return view;
+ }
+
+ /**
+ * Retrieve a reference to this activity's ActionBar.
+ *
+ * @return The Activity's ActionBar, or null if it does not have one.
+ */
+ @Nullable
+ public ActionBar getActionBar() {
+ initWindowDecorActionBar();
+ return mActionBar;
+ }
+
+ /**
+ * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link ActionBar} for this
+ * Activity window.
+ *
+ * <p>When set to a non-null value the {@link #getActionBar()} method will return
+ * an {@link ActionBar} object that can be used to control the given toolbar as if it were
+ * a traditional window decor action bar. The toolbar's menu will be populated with the
+ * Activity's options menu and the navigation button will be wired through the standard
+ * {@link android.R.id#home home} menu select action.</p>
+ *
+ * <p>In order to use a Toolbar within the Activity's window content the application
+ * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
+ *
+ * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
+ */
+ public void setActionBar(@Nullable Toolbar toolbar) {
+ final ActionBar ab = getActionBar();
+ if (ab instanceof WindowDecorActionBar) {
+ throw new IllegalStateException("This Activity already has an action bar supplied " +
+ "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
+ "android:windowActionBar to false in your theme to use a Toolbar instead.");
+ }
+
+ // If we reach here then we're setting a new action bar
+ // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
+ mMenuInflater = null;
+
+ // If we have an action bar currently, destroy it
+ if (ab != null) {
+ ab.onDestroy();
+ }
+
+ if (toolbar != null) {
+ final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(), this);
+ mActionBar = tbab;
+ mWindow.setCallback(tbab.getWrappedWindowCallback());
+ } else {
+ mActionBar = null;
+ // Re-set the original window callback since we may have already set a Toolbar wrapper
+ mWindow.setCallback(this);
+ }
+
+ invalidateOptionsMenu();
+ }
+
+ /**
+ * Creates a new ActionBar, locates the inflated ActionBarView,
+ * initializes the ActionBar with the view, and sets mActionBar.
+ */
+ private void initWindowDecorActionBar() {
+ Window window = getWindow();
+
+ // Initializing the window decor can change window feature flags.
+ // Make sure that we have the correct set before performing the test below.
+ window.getDecorView();
+
+ if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
+ return;
+ }
+
+ mActionBar = new WindowDecorActionBar(this);
+ mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+
+ mWindow.setDefaultIcon(mActivityInfo.getIconResource());
+ mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
+ }
+
+ /**
+ * Set the activity content from a layout resource. The resource will be
+ * inflated, adding all top-level views to the activity.
+ *
+ * @param layoutResID Resource ID to be inflated.
+ *
+ * @see #setContentView(android.view.View)
+ * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+ */
+ public void setContentView(@LayoutRes int layoutResID) {
+ getWindow().setContentView(layoutResID);
+ initWindowDecorActionBar();
+ }
+
+ /**
+ * Set the activity content to an explicit view. This view is placed
+ * directly into the activity's view hierarchy. It can itself be a complex
+ * view hierarchy. When calling this method, the layout parameters of the
+ * specified view are ignored. Both the width and the height of the view are
+ * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
+ * your own layout parameters, invoke
+ * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
+ * instead.
+ *
+ * @param view The desired content to display.
+ *
+ * @see #setContentView(int)
+ * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+ */
+ public void setContentView(View view) {
+ getWindow().setContentView(view);
+ initWindowDecorActionBar();
+ }
+
+ /**
+ * Set the activity content to an explicit view. This view is placed
+ * directly into the activity's view hierarchy. It can itself be a complex
+ * view hierarchy.
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ *
+ * @see #setContentView(android.view.View)
+ * @see #setContentView(int)
+ */
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getWindow().setContentView(view, params);
+ initWindowDecorActionBar();
+ }
+
+ /**
+ * Add an additional content view to the activity. Added after any existing
+ * ones in the activity -- existing views are NOT removed.
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ */
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getWindow().addContentView(view, params);
+ initWindowDecorActionBar();
+ }
+
+ /**
+ * Retrieve the {@link TransitionManager} responsible for default transitions in this window.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return non-null after content has been initialized (e.g. by using
+ * {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+ *
+ * @return This window's content TransitionManager or null if none is set.
+ */
+ public TransitionManager getContentTransitionManager() {
+ return getWindow().getTransitionManager();
+ }
+
+ /**
+ * Set the {@link TransitionManager} to use for default transitions in this window.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param tm The TransitionManager to use for scene changes.
+ */
+ public void setContentTransitionManager(TransitionManager tm) {
+ getWindow().setTransitionManager(tm);
+ }
+
+ /**
+ * Retrieve the {@link Scene} representing this window's current content.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return null if the current content is not represented by a Scene.</p>
+ *
+ * @return Current Scene being shown or null
+ */
+ public Scene getContentScene() {
+ return getWindow().getContentScene();
+ }
+
+ /**
+ * Sets whether this activity is finished when touched outside its window's
+ * bounds.
+ */
+ public void setFinishOnTouchOutside(boolean finish) {
+ mWindow.setCloseOnTouchOutside(finish);
+ }
+
+ /** @hide */
+ @IntDef(prefix = { "DEFAULT_KEYS_" }, value = {
+ DEFAULT_KEYS_DISABLE,
+ DEFAULT_KEYS_DIALER,
+ DEFAULT_KEYS_SHORTCUT,
+ DEFAULT_KEYS_SEARCH_LOCAL,
+ DEFAULT_KEYS_SEARCH_GLOBAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DefaultKeyMode {}
+
+ /**
+ * Use with {@link #setDefaultKeyMode} to turn off default handling of
+ * keys.
+ *
+ * @see #setDefaultKeyMode
+ */
+ static public final int DEFAULT_KEYS_DISABLE = 0;
+ /**
+ * Use with {@link #setDefaultKeyMode} to launch the dialer during default
+ * key handling.
+ *
+ * @see #setDefaultKeyMode
+ */
+ static public final int DEFAULT_KEYS_DIALER = 1;
+ /**
+ * Use with {@link #setDefaultKeyMode} to execute a menu shortcut in
+ * default key handling.
+ *
+ * <p>That is, the user does not need to hold down the menu key to execute menu shortcuts.
+ *
+ * @see #setDefaultKeyMode
+ */
+ static public final int DEFAULT_KEYS_SHORTCUT = 2;
+ /**
+ * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
+ * will start an application-defined search. (If the application or activity does not
+ * actually define a search, the keys will be ignored.)
+ *
+ * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
+ *
+ * @see #setDefaultKeyMode
+ */
+ static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3;
+
+ /**
+ * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
+ * will start a global search (typically web search, but some platforms may define alternate
+ * methods for global search)
+ *
+ * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
+ *
+ * @see #setDefaultKeyMode
+ */
+ static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4;
+
+ /**
+ * Select the default key handling for this activity. This controls what
+ * will happen to key events that are not otherwise handled. The default
+ * mode ({@link #DEFAULT_KEYS_DISABLE}) will simply drop them on the
+ * floor. Other modes allow you to launch the dialer
+ * ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options
+ * menu without requiring the menu key be held down
+ * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL}
+ * and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}).
+ *
+ * <p>Note that the mode selected here does not impact the default
+ * handling of system keys, such as the "back" and "menu" keys, and your
+ * activity and its views always get a first chance to receive and handle
+ * all application keys.
+ *
+ * @param mode The desired default key mode constant.
+ *
+ * @see #onKeyDown
+ */
+ public final void setDefaultKeyMode(@DefaultKeyMode int mode) {
+ mDefaultKeyMode = mode;
+
+ // Some modes use a SpannableStringBuilder to track & dispatch input events
+ // This list must remain in sync with the switch in onKeyDown()
+ switch (mode) {
+ case DEFAULT_KEYS_DISABLE:
+ case DEFAULT_KEYS_SHORTCUT:
+ mDefaultKeySsb = null; // not used in these modes
+ break;
+ case DEFAULT_KEYS_DIALER:
+ case DEFAULT_KEYS_SEARCH_LOCAL:
+ case DEFAULT_KEYS_SEARCH_GLOBAL:
+ mDefaultKeySsb = new SpannableStringBuilder();
+ Selection.setSelection(mDefaultKeySsb,0);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Called when a key was pressed down and not handled by any of the views
+ * inside of the activity. So, for example, key presses while the cursor
+ * is inside a TextView will not trigger the event (unless it is a navigation
+ * to another object) because TextView handles its own key presses.
+ *
+ * <p>If the focused view didn't want this event, this method is called.
+ *
+ * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
+ * by calling {@link #onBackPressed()}, though the behavior varies based
+ * on the application compatibility mode: for
+ * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications,
+ * it will set up the dispatch to call {@link #onKeyUp} where the action
+ * will be performed; for earlier applications, it will perform the
+ * action immediately in on-down, as those versions of the platform
+ * behaved.
+ *
+ * <p>Other additional default key handling may be performed
+ * if configured with {@link #setDefaultKeyMode}.
+ *
+ * @return Return <code>true</code> to prevent this event from being propagated
+ * further, or <code>false</code> to indicate that you have not handled
+ * this event and it should continue to be propagated.
+ * @see #onKeyUp
+ * @see android.view.KeyEvent
+ */
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.ECLAIR) {
+ event.startTracking();
+ } else {
+ onBackPressed();
+ }
+ return true;
+ }
+
+ if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
+ return false;
+ } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
+ Window w = getWindow();
+ if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
+ Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
+ return true;
+ }
+ return false;
+ } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+ // Don't consume TAB here since it's used for navigation. Arrow keys
+ // aren't considered "typing keys" so they already won't get consumed.
+ return false;
+ } else {
+ // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
+ boolean clearSpannable = false;
+ boolean handled;
+ if ((event.getRepeatCount() != 0) || event.isSystem()) {
+ clearSpannable = true;
+ handled = false;
+ } else {
+ handled = TextKeyListener.getInstance().onKeyDown(
+ null, mDefaultKeySsb, keyCode, event);
+ if (handled && mDefaultKeySsb.length() > 0) {
+ // something useable has been typed - dispatch it now.
+
+ final String str = mDefaultKeySsb.toString();
+ clearSpannable = true;
+
+ switch (mDefaultKeyMode) {
+ case DEFAULT_KEYS_DIALER:
+ Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ break;
+ case DEFAULT_KEYS_SEARCH_LOCAL:
+ startSearch(str, false, null, false);
+ break;
+ case DEFAULT_KEYS_SEARCH_GLOBAL:
+ startSearch(str, false, null, true);
+ break;
+ }
+ }
+ }
+ if (clearSpannable) {
+ mDefaultKeySsb.clear();
+ mDefaultKeySsb.clearSpans();
+ Selection.setSelection(mDefaultKeySsb,0);
+ }
+ return handled;
+ }
+ }
+
+ /**
+ * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+ * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+ * the event).
+ *
+ * To receive this callback, you must return true from onKeyDown for the current
+ * event stream.
+ *
+ * @see KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+ */
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Called when a key was released and not handled by any of the views
+ * inside of the activity. So, for example, key presses while the cursor
+ * is inside a TextView will not trigger the event (unless it is a navigation
+ * to another object) because TextView handles its own key presses.
+ *
+ * <p>The default implementation handles KEYCODE_BACK to stop the activity
+ * and go back.
+ *
+ * @return Return <code>true</code> to prevent this event from being propagated
+ * further, or <code>false</code> to indicate that you have not handled
+ * this event and it should continue to be propagated.
+ * @see #onKeyDown
+ * @see KeyEvent
+ */
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ int sdkVersion = getApplicationInfo().targetSdkVersion;
+ if (sdkVersion >= Build.VERSION_CODES.ECLAIR) {
+ if (keyCode == KeyEvent.KEYCODE_BACK
+ && event.isTracking()
+ && !event.isCanceled()
+ && mDefaultBackCallback == null) {
+ // Using legacy back handling.
+ onBackPressed();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+ * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+ * the event).
+ */
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ return false;
+ }
+
+ private static final class RequestFinishCallback extends IRequestFinishCallback.Stub {
+ private final WeakReference<Activity> mActivityRef;
+
+ RequestFinishCallback(WeakReference<Activity> activityRef) {
+ mActivityRef = activityRef;
+ }
+
+ @Override
+ public void requestFinish() {
+ Activity activity = mActivityRef.get();
+ if (activity != null) {
+ activity.mHandler.post(activity::finishAfterTransition);
+ }
+ }
+ }
+
+ /**
+ * Called when the activity has detected the user's press of the back key. The default
+ * implementation depends on the platform version:
+ *
+ * <ul>
+ * <li>On platform versions prior to {@link android.os.Build.VERSION_CODES#S}, it
+ * finishes the current activity, but you can override this to do whatever you want.
+ *
+ * <li><p>Starting with platform version {@link android.os.Build.VERSION_CODES#S}, for
+ * activities that are the root activity of the task and also declare an
+ * {@link android.content.IntentFilter} with {@link Intent#ACTION_MAIN} and
+ * {@link Intent#CATEGORY_LAUNCHER} in the manifest, the current activity and its
+ * task will be moved to the back of the activity stack instead of being finished.
+ * Other activities will simply be finished.
+ *
+ * <li><p>If you target version {@link android.os.Build.VERSION_CODES#S} and
+ * override this method, we strongly recommend to call through to the superclass
+ * implementation after you finish handling navigation within the app.
+ *
+ * <li><p>If you target version {@link android.os.Build.VERSION_CODES#TIRAMISU} or later,
+ * you should not use this method but register an {@link OnBackInvokedCallback} on an
+ * {@link OnBackInvokedDispatcher} that you can retrieve using
+ * {@link #getOnBackInvokedDispatcher()}. You should also set
+ * {@code android:enableOnBackInvokedCallback="true"} in the application manifest.
+ * <p>Alternatively, you can use
+ * {@code androidx.activity.ComponentActivity#getOnBackPressedDispatcher()}
+ * for backward compatibility.
+ * </ul>
+ *
+ * @see #moveTaskToBack(boolean)
+ *
+ * @deprecated Use {@link OnBackInvokedCallback} or
+ * {@code androidx.activity.OnBackPressedCallback} to handle back navigation instead.
+ * <p>
+ * Starting from Android 13 (API level 33), back event handling is
+ * moving to an ahead-of-time model and {@link Activity#onBackPressed()} and
+ * {@link KeyEvent#KEYCODE_BACK} should not be used to handle back events (back gesture or
+ * back button click). Instead, an {@link OnBackInvokedCallback} should be registered using
+ * {@link Activity#getOnBackInvokedDispatcher()}
+ * {@link OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)
+ * .registerOnBackInvokedCallback(priority, callback)}.
+ */
+ @Deprecated
+ public void onBackPressed() {
+ if (mActionBar != null && mActionBar.collapseActionView()) {
+ return;
+ }
+
+ FragmentManager fragmentManager = mFragments.getFragmentManager();
+
+ if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
+ return;
+ }
+ onBackInvoked();
+ }
+
+ private void onBackInvoked() {
+ // Inform activity task manager that the activity received a back press.
+ // This call allows ActivityTaskManager to intercept or move the task
+ // to the back when needed.
+ ActivityClient.getInstance().onBackPressed(mToken,
+ new RequestFinishCallback(new WeakReference<>(this)));
+
+ if (isTaskRoot()) {
+ getAutofillClientController().onActivityBackPressed(mIntent);
+ }
+ }
+
+ /**
+ * Called when a key shortcut event is not handled by any of the views in the Activity.
+ * Override this method to implement global key shortcuts for the Activity.
+ * Key shortcuts can also be implemented by setting the
+ * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return True if the key shortcut was handled.
+ */
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ // Let the Action Bar have a chance at handling the shortcut.
+ ActionBar actionBar = getActionBar();
+ return (actionBar != null && actionBar.onKeyShortcut(keyCode, event));
+ }
+
+ /**
+ * Called when a touch screen event was not handled by any of the views
+ * inside of the activity. This is most useful to process touch events that happen
+ * outside of your window bounds, where there is no view to receive it.
+ *
+ * @param event The touch screen event being processed.
+ *
+ * @return Return true if you have consumed the event, false if you haven't.
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mWindow.shouldCloseOnTouch(this, event)) {
+ finish();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when the trackball was moved and not handled by any of the
+ * views inside of the activity. So, for example, if the trackball moves
+ * while focus is on a button, you will receive a call here because
+ * buttons do not normally do anything with trackball events. The call
+ * here happens <em>before</em> trackball movements are converted to
+ * DPAD key events, which then get sent back to the view hierarchy, and
+ * will be processed at the point for things like focus navigation.
+ *
+ * @param event The trackball event being processed.
+ *
+ * @return Return true if you have consumed the event, false if you haven't.
+ * The default implementation always returns false.
+ */
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Called when a generic motion event was not handled by any of the
+ * views inside of the activity.
+ * <p>
+ * Generic motion events describe joystick movements, hover events from mouse or stylus
+ * devices, trackpad touches, scroll wheel movements and other motion events not handled
+ * by {@link #onTouchEvent(MotionEvent)} or {@link #onTrackballEvent(MotionEvent)}.
+ * The {@link MotionEvent#getSource() source} of the motion event specifies
+ * the class of input that was received. Implementations of this method
+ * must examine the bits in the source before processing the event.
+ * </p><p>
+ * Generic motion events with source class
+ * {@link android.view.InputDevice#SOURCE_CLASS_POINTER}
+ * are delivered to the view under the pointer. All other generic motion events are
+ * delivered to the focused view.
+ * </p><p>
+ * See {@link View#onGenericMotionEvent(MotionEvent)} for an example of how to
+ * handle this event.
+ * </p>
+ *
+ * @param event The generic motion event being processed.
+ *
+ * @return Return true if you have consumed the event, false if you haven't.
+ * The default implementation always returns false.
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Called whenever a key, touch, or trackball event is dispatched to the
+ * activity. Implement this method if you wish to know that the user has
+ * interacted with the device in some way while your activity is running.
+ * This callback and {@link #onUserLeaveHint} are intended to help
+ * activities manage status bar notifications intelligently; specifically,
+ * for helping activities determine the proper time to cancel a notification.
+ *
+ * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
+ * be accompanied by calls to {@link #onUserInteraction}. This
+ * ensures that your activity will be told of relevant user activity such
+ * as pulling down the notification pane and touching an item there.
+ *
+ * <p>Note that this callback will be invoked for the touch down action
+ * that begins a touch gesture, but may not be invoked for the touch-moved
+ * and touch-up actions that follow.
+ *
+ * @see #onUserLeaveHint()
+ */
+ public void onUserInteraction() {
+ }
+
+ public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+ // Update window manager if: we have a view, that view is
+ // attached to its parent (which will be a RootView), and
+ // this activity is not embedded.
+ if (mParent == null) {
+ View decor = mDecor;
+ if (decor != null && decor.getParent() != null) {
+ getWindowManager().updateViewLayout(decor, params);
+ if (mContentCaptureManager != null) {
+ mContentCaptureManager.updateWindowAttributes(params);
+ }
+ }
+ }
+ }
+
+ public void onContentChanged() {
+ }
+
+ /**
+ * Called when the current {@link Window} of the activity gains or loses
+ * focus. This is the best indicator of whether this activity is the entity
+ * with which the user actively interacts. The default implementation
+ * clears the key tracking state, so should always be called.
+ *
+ * <p>Note that this provides information about global focus state, which
+ * is managed independently of activity lifecycle. As such, while focus
+ * changes will generally have some relation to lifecycle changes (an
+ * activity that is stopped will not generally get window focus), you
+ * should not rely on any particular order between the callbacks here and
+ * those in the other lifecycle methods such as {@link #onResume}.
+ *
+ * <p>As a general rule, however, a foreground activity will have window
+ * focus... unless it has displayed other dialogs or popups that take
+ * input focus, in which case the activity itself will not have focus
+ * when the other windows have it. Likewise, the system may display
+ * system-level windows (such as the status bar notification panel or
+ * a system alert) which will temporarily take window input focus without
+ * pausing the foreground activity.
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} there can be
+ * multiple resumed activities at the same time in multi-window mode, so
+ * resumed state does not guarantee window focus even if there are no
+ * overlays above.
+ *
+ * <p>If the intent is to know when an activity is the topmost active, the
+ * one the user interacted with last among all activities but not including
+ * non-activity windows like dialogs and popups, then
+ * {@link #onTopResumedActivityChanged(boolean)} should be used. On platform
+ * versions prior to {@link android.os.Build.VERSION_CODES#Q},
+ * {@link #onResume} is the best indicator.
+ *
+ * @param hasFocus Whether the window of this activity has focus.
+ *
+ * @see #hasWindowFocus()
+ * @see #onResume
+ * @see View#onWindowFocusChanged(boolean)
+ * @see #onTopResumedActivityChanged(boolean)
+ */
+ public void onWindowFocusChanged(boolean hasFocus) {
+ }
+
+ /**
+ * Called when the main window associated with the activity has been
+ * attached to the window manager.
+ * See {@link View#onAttachedToWindow() View.onAttachedToWindow()}
+ * for more information.
+ * @see View#onAttachedToWindow
+ */
+ public void onAttachedToWindow() {
+ }
+
+ /**
+ * Called when the main window associated with the activity has been
+ * detached from the window manager.
+ * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
+ * for more information.
+ * @see View#onDetachedFromWindow
+ */
+ public void onDetachedFromWindow() {
+ }
+
+ /**
+ * Returns true if this activity's <em>main</em> window currently has window focus.
+ * Note that this is not the same as the view itself having focus.
+ *
+ * @return True if this activity's main window currently has window focus.
+ *
+ * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
+ */
+ public boolean hasWindowFocus() {
+ Window w = getWindow();
+ if (w != null) {
+ View d = w.getDecorView();
+ if (d != null) {
+ return d.hasWindowFocus();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when the main window associated with the activity has been dismissed.
+ * @hide
+ */
+ @Override
+ public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
+ finish(finishTask ? FINISH_TASK_WITH_ACTIVITY : DONT_FINISH_TASK_WITH_ACTIVITY);
+ if (suppressWindowTransition) {
+ overridePendingTransition(0, 0);
+ }
+ }
+
+
+ /**
+ * Called to process key events. You can override this to intercept all
+ * key events before they are dispatched to the window. Be sure to call
+ * this implementation for key events that should be handled normally.
+ *
+ * @param event The key event.
+ *
+ * @return boolean Return true if this event was consumed.
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ onUserInteraction();
+
+ // Let action bars open menus in response to the menu key prioritized over
+ // the window handling it
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_MENU &&
+ mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+ return true;
+ }
+
+ Window win = getWindow();
+ if (win.superDispatchKeyEvent(event)) {
+ return true;
+ }
+ View decor = mDecor;
+ if (decor == null) decor = win.getDecorView();
+ return event.dispatch(this, decor != null
+ ? decor.getKeyDispatcherState() : null, this);
+ }
+
+ /**
+ * Called to process a key shortcut event.
+ * You can override this to intercept all key shortcut events before they are
+ * dispatched to the window. Be sure to call this implementation for key shortcut
+ * events that should be handled normally.
+ *
+ * @param event The key shortcut event.
+ * @return True if this event was consumed.
+ */
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ onUserInteraction();
+ if (getWindow().superDispatchKeyShortcutEvent(event)) {
+ return true;
+ }
+ return onKeyShortcut(event.getKeyCode(), event);
+ }
+
+ /**
+ * Called to process touch screen events. You can override this to
+ * intercept all touch screen events before they are dispatched to the
+ * window. Be sure to call this implementation for touch screen events
+ * that should be handled normally.
+ *
+ * @param ev The touch screen event.
+ *
+ * @return boolean Return true if this event was consumed.
+ *
+ * @see #onTouchEvent(MotionEvent)
+ */
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ onUserInteraction();
+ }
+ if (getWindow().superDispatchTouchEvent(ev)) {
+ return true;
+ }
+ return onTouchEvent(ev);
+ }
+
+ /**
+ * Called to process trackball events. You can override this to
+ * intercept all trackball events before they are dispatched to the
+ * window. Be sure to call this implementation for trackball events
+ * that should be handled normally.
+ *
+ * @param ev The trackball event.
+ *
+ * @return boolean Return true if this event was consumed.
+ *
+ * @see #onTrackballEvent(MotionEvent)
+ */
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ onUserInteraction();
+ if (getWindow().superDispatchTrackballEvent(ev)) {
+ return true;
+ }
+ return onTrackballEvent(ev);
+ }
+
+ /**
+ * Called to process generic motion events. You can override this to
+ * intercept all generic motion events before they are dispatched to the
+ * window. Be sure to call this implementation for generic motion events
+ * that should be handled normally.
+ *
+ * @param ev The generic motion event.
+ *
+ * @return boolean Return true if this event was consumed.
+ *
+ * @see #onGenericMotionEvent(MotionEvent)
+ */
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ onUserInteraction();
+ if (getWindow().superDispatchGenericMotionEvent(ev)) {
+ return true;
+ }
+ return onGenericMotionEvent(ev);
+ }
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
+ event.setPackageName(getPackageName());
+
+ LayoutParams params = getWindow().getAttributes();
+ boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
+ (params.height == LayoutParams.MATCH_PARENT);
+ event.setFullScreen(isFullScreen);
+
+ CharSequence title = getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ event.getText().add(title);
+ }
+
+ return true;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.Window.Callback#onCreatePanelView}
+ * for activities. This
+ * simply returns null so that all panel sub-windows will have the default
+ * menu behavior.
+ */
+ @Nullable
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.Window.Callback#onCreatePanelMenu}
+ * for activities. This calls through to the new
+ * {@link #onCreateOptionsMenu} method for the
+ * {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel,
+ * so that subclasses of Activity don't need to deal with feature codes.
+ */
+ public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
+ if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+ boolean show = onCreateOptionsMenu(menu);
+ show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
+ return show;
+ }
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.Window.Callback#onPreparePanel}
+ * for activities. This
+ * calls through to the new {@link #onPrepareOptionsMenu} method for the
+ * {@link android.view.Window#FEATURE_OPTIONS_PANEL}
+ * panel, so that subclasses of
+ * Activity don't need to deal with feature codes.
+ */
+ public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
+ if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+ boolean goforit = onPrepareOptionsMenu(menu);
+ goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
+ return goforit;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return The default implementation returns true.
+ */
+ @Override
+ public boolean onMenuOpened(int featureId, @NonNull Menu menu) {
+ if (featureId == Window.FEATURE_ACTION_BAR) {
+ initWindowDecorActionBar();
+ if (mActionBar != null) {
+ mActionBar.dispatchMenuVisibilityChanged(true);
+ } else {
+ Log.e(TAG, "Tried to open action bar menu with no action bar");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.Window.Callback#onMenuItemSelected}
+ * for activities. This calls through to the new
+ * {@link #onOptionsItemSelected} method for the
+ * {@link android.view.Window#FEATURE_OPTIONS_PANEL}
+ * panel, so that subclasses of
+ * Activity don't need to deal with feature codes.
+ */
+ public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
+ CharSequence titleCondensed = item.getTitleCondensed();
+
+ switch (featureId) {
+ case Window.FEATURE_OPTIONS_PANEL:
+ // Put event logging here so it gets called even if subclass
+ // doesn't call through to superclass's implmeentation of each
+ // of these methods below
+ if(titleCondensed != null) {
+ EventLog.writeEvent(50000, 0, titleCondensed.toString());
+ }
+ if (onOptionsItemSelected(item)) {
+ return true;
+ }
+ if (mFragments.dispatchOptionsItemSelected(item)) {
+ return true;
+ }
+ if (item.getItemId() == android.R.id.home && mActionBar != null &&
+ (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ if (mParent == null) {
+ return onNavigateUp();
+ } else {
+ return mParent.onNavigateUpFromChild(this);
+ }
+ }
+ return false;
+
+ case Window.FEATURE_CONTEXT_MENU:
+ if(titleCondensed != null) {
+ EventLog.writeEvent(50000, 1, titleCondensed.toString());
+ }
+ if (onContextItemSelected(item)) {
+ return true;
+ }
+ return mFragments.dispatchContextItemSelected(item);
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for
+ * activities. This calls through to {@link #onOptionsMenuClosed(Menu)}
+ * method for the {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel,
+ * so that subclasses of Activity don't need to deal with feature codes.
+ * For context menus ({@link Window#FEATURE_CONTEXT_MENU}), the
+ * {@link #onContextMenuClosed(Menu)} will be called.
+ */
+ public void onPanelClosed(int featureId, @NonNull Menu menu) {
+ switch (featureId) {
+ case Window.FEATURE_OPTIONS_PANEL:
+ mFragments.dispatchOptionsMenuClosed(menu);
+ onOptionsMenuClosed(menu);
+ break;
+
+ case Window.FEATURE_CONTEXT_MENU:
+ onContextMenuClosed(menu);
+ break;
+
+ case Window.FEATURE_ACTION_BAR:
+ initWindowDecorActionBar();
+ mActionBar.dispatchMenuVisibilityChanged(false);
+ break;
+ }
+ }
+
+ /**
+ * Declare that the options menu has changed, so should be recreated.
+ * The {@link #onCreateOptionsMenu(Menu)} method will be called the next
+ * time it needs to be displayed.
+ */
+ public void invalidateOptionsMenu() {
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ (mActionBar == null || !mActionBar.invalidateOptionsMenu())) {
+ mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ }
+ }
+
+ /**
+ * Initialize the contents of the Activity's standard options menu. You
+ * should place your menu items in to <var>menu</var>.
+ *
+ * <p>This is only called once, the first time the options menu is
+ * displayed. To update the menu every time it is displayed, see
+ * {@link #onPrepareOptionsMenu}.
+ *
+ * <p>The default implementation populates the menu with standard system
+ * menu items. These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that
+ * they will be correctly ordered with application-defined menu items.
+ * Deriving classes should always call through to the base implementation.
+ *
+ * <p>You can safely hold on to <var>menu</var> (and any items created
+ * from it), making modifications to it as desired, until the next
+ * time onCreateOptionsMenu() is called.
+ *
+ * <p>When you add items to the menu, you can implement the Activity's
+ * {@link #onOptionsItemSelected} method to handle them there.
+ *
+ * @param menu The options menu in which you place your items.
+ *
+ * @return You must return true for the menu to be displayed;
+ * if you return false it will not be shown.
+ *
+ * @see #onPrepareOptionsMenu
+ * @see #onOptionsItemSelected
+ */
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mParent != null) {
+ return mParent.onCreateOptionsMenu(menu);
+ }
+ return true;
+ }
+
+ /**
+ * Prepare the Screen's standard options menu to be displayed. This is
+ * called right before the menu is shown, every time it is shown. You can
+ * use this method to efficiently enable/disable items or otherwise
+ * dynamically modify the contents.
+ *
+ * <p>The default implementation updates the system menu items based on the
+ * activity's state. Deriving classes should always call through to the
+ * base class implementation.
+ *
+ * @param menu The options menu as last shown or first initialized by
+ * onCreateOptionsMenu().
+ *
+ * @return You must return true for the menu to be displayed;
+ * if you return false it will not be shown.
+ *
+ * @see #onCreateOptionsMenu
+ */
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (mParent != null) {
+ return mParent.onPrepareOptionsMenu(menu);
+ }
+ return true;
+ }
+
+ /**
+ * This hook is called whenever an item in your options menu is selected.
+ * The default implementation simply returns false to have the normal
+ * processing happen (calling the item's Runnable or sending a message to
+ * its Handler as appropriate). You can use this method for any items
+ * for which you would like to do processing without those other
+ * facilities.
+ *
+ * <p>Derived classes should call through to the base class for it to
+ * perform the default menu handling.</p>
+ *
+ * @param item The menu item that was selected.
+ *
+ * @return boolean Return false to allow normal menu processing to
+ * proceed, true to consume it here.
+ *
+ * @see #onCreateOptionsMenu
+ */
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (mParent != null) {
+ return mParent.onOptionsItemSelected(item);
+ }
+ return false;
+ }
+
+ /**
+ * This method is called whenever the user chooses to navigate Up within your application's
+ * activity hierarchy from the action bar.
+ *
+ * <p>If the attribute {@link android.R.attr#parentActivityName parentActivityName}
+ * was specified in the manifest for this activity or an activity-alias to it,
+ * default Up navigation will be handled automatically. If any activity
+ * along the parent chain requires extra Intent arguments, the Activity subclass
+ * should override the method {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}
+ * to supply those arguments.</p>
+ *
+ * <p>See <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back Stack</a>
+ * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
+ * from the design guide for more information about navigating within your app.</p>
+ *
+ * <p>See the {@link TaskStackBuilder} class and the Activity methods
+ * {@link #getParentActivityIntent()}, {@link #shouldUpRecreateTask(Intent)}, and
+ * {@link #navigateUpTo(Intent)} for help implementing custom Up navigation.
+ * The AppNavigation sample application in the Android SDK is also available for reference.</p>
+ *
+ * @return true if Up navigation completed successfully and this Activity was finished,
+ * false otherwise.
+ */
+ public boolean onNavigateUp() {
+ // Automatically handle hierarchical Up navigation if the proper
+ // metadata is available.
+ Intent upIntent = getParentActivityIntent();
+ if (upIntent != null) {
+ if (mActivityInfo.taskAffinity == null) {
+ // Activities with a null affinity are special; they really shouldn't
+ // specify a parent activity intent in the first place. Just finish
+ // the current activity and call it a day.
+ finish();
+ } else if (shouldUpRecreateTask(upIntent)) {
+ TaskStackBuilder b = TaskStackBuilder.create(this);
+ onCreateNavigateUpTaskStack(b);
+ onPrepareNavigateUpTaskStack(b);
+ b.startActivities();
+
+ // We can't finishAffinity if we have a result.
+ // Fall back and simply finish the current activity instead.
+ if (mResultCode != RESULT_CANCELED || mResultData != null) {
+ // Tell the developer what's going on to avoid hair-pulling.
+ Log.i(TAG, "onNavigateUp only finishing topmost activity to return a result");
+ finish();
+ } else {
+ finishAffinity();
+ }
+ } else {
+ navigateUpTo(upIntent);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This is called when a child activity of this one attempts to navigate up.
+ * The default implementation simply calls onNavigateUp() on this activity (the parent).
+ *
+ * @param child The activity making the call.
+ * @deprecated Use {@link #onNavigateUp()} instead.
+ */
+ @Deprecated
+ public boolean onNavigateUpFromChild(Activity child) {
+ return onNavigateUp();
+ }
+
+ /**
+ * Define the synthetic task stack that will be generated during Up navigation from
+ * a different task.
+ *
+ * <p>The default implementation of this method adds the parent chain of this activity
+ * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications
+ * may choose to override this method to construct the desired task stack in a different
+ * way.</p>
+ *
+ * <p>This method will be invoked by the default implementation of {@link #onNavigateUp()}
+ * if {@link #shouldUpRecreateTask(Intent)} returns true when supplied with the intent
+ * returned by {@link #getParentActivityIntent()}.</p>
+ *
+ * <p>Applications that wish to supply extra Intent parameters to the parent stack defined
+ * by the manifest should override {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}.</p>
+ *
+ * @param builder An empty TaskStackBuilder - the application should add intents representing
+ * the desired task stack
+ */
+ public void onCreateNavigateUpTaskStack(TaskStackBuilder builder) {
+ builder.addParentStack(this);
+ }
+
+ /**
+ * Prepare the synthetic task stack that will be generated during Up navigation
+ * from a different task.
+ *
+ * <p>This method receives the {@link TaskStackBuilder} with the constructed series of
+ * Intents as generated by {@link #onCreateNavigateUpTaskStack(TaskStackBuilder)}.
+ * If any extra data should be added to these intents before launching the new task,
+ * the application should override this method and add that data here.</p>
+ *
+ * @param builder A TaskStackBuilder that has been populated with Intents by
+ * onCreateNavigateUpTaskStack.
+ */
+ public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) {
+ }
+
+ /**
+ * This hook is called whenever the options menu is being closed (either by the user canceling
+ * the menu with the back/menu button, or when an item is selected).
+ *
+ * @param menu The options menu as last shown or first initialized by
+ * onCreateOptionsMenu().
+ */
+ public void onOptionsMenuClosed(Menu menu) {
+ if (mParent != null) {
+ mParent.onOptionsMenuClosed(menu);
+ }
+ }
+
+ /**
+ * Programmatically opens the options menu. If the options menu is already
+ * open, this method does nothing.
+ */
+ public void openOptionsMenu() {
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ (mActionBar == null || !mActionBar.openOptionsMenu())) {
+ mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ }
+ }
+
+ /**
+ * Progammatically closes the options menu. If the options menu is already
+ * closed, this method does nothing.
+ */
+ public void closeOptionsMenu() {
+ if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+ (mActionBar == null || !mActionBar.closeOptionsMenu())) {
+ mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+ }
+ }
+
+ /**
+ * Called when a context menu for the {@code view} is about to be shown.
+ * Unlike {@link #onCreateOptionsMenu(Menu)}, this will be called every
+ * time the context menu is about to be shown and should be populated for
+ * the view (or item inside the view for {@link AdapterView} subclasses,
+ * this can be found in the {@code menuInfo})).
+ * <p>
+ * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an
+ * item has been selected.
+ * <p>
+ * It is not safe to hold onto the context menu after this method returns.
+ *
+ */
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ }
+
+ /**
+ * Registers a context menu to be shown for the given view (multiple views
+ * can show the context menu). This method will set the
+ * {@link OnCreateContextMenuListener} on the view to this activity, so
+ * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
+ * called when it is time to show the context menu.
+ *
+ * @see #unregisterForContextMenu(View)
+ * @param view The view that should show a context menu.
+ */
+ public void registerForContextMenu(View view) {
+ view.setOnCreateContextMenuListener(this);
+ }
+
+ /**
+ * Prevents a context menu to be shown for the given view. This method will remove the
+ * {@link OnCreateContextMenuListener} on the view.
+ *
+ * @see #registerForContextMenu(View)
+ * @param view The view that should stop showing a context menu.
+ */
+ public void unregisterForContextMenu(View view) {
+ view.setOnCreateContextMenuListener(null);
+ }
+
+ /**
+ * Programmatically opens the context menu for a particular {@code view}.
+ * The {@code view} should have been added via
+ * {@link #registerForContextMenu(View)}.
+ *
+ * @param view The view to show the context menu for.
+ */
+ public void openContextMenu(View view) {
+ view.showContextMenu();
+ }
+
+ /**
+ * Programmatically closes the most recently opened context menu, if showing.
+ */
+ public void closeContextMenu() {
+ if (mWindow.hasFeature(Window.FEATURE_CONTEXT_MENU)) {
+ mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
+ }
+ }
+
+ /**
+ * This hook is called whenever an item in a context menu is selected. The
+ * default implementation simply returns false to have the normal processing
+ * happen (calling the item's Runnable or sending a message to its Handler
+ * as appropriate). You can use this method for any items for which you
+ * would like to do processing without those other facilities.
+ * <p>
+ * Use {@link MenuItem#getMenuInfo()} to get extra information set by the
+ * View that added this menu item.
+ * <p>
+ * Derived classes should call through to the base class for it to perform
+ * the default menu handling.
+ *
+ * @param item The context menu item that was selected.
+ * @return boolean Return false to allow normal context menu processing to
+ * proceed, true to consume it here.
+ */
+ public boolean onContextItemSelected(@NonNull MenuItem item) {
+ if (mParent != null) {
+ return mParent.onContextItemSelected(item);
+ }
+ return false;
+ }
+
+ /**
+ * This hook is called whenever the context menu is being closed (either by
+ * the user canceling the menu with the back/menu button, or when an item is
+ * selected).
+ *
+ * @param menu The context menu that is being closed.
+ */
+ public void onContextMenuClosed(@NonNull Menu menu) {
+ if (mParent != null) {
+ mParent.onContextMenuClosed(menu);
+ }
+ }
+
+ /**
+ * @deprecated Old no-arguments version of {@link #onCreateDialog(int, Bundle)}.
+ */
+ @Deprecated
+ protected Dialog onCreateDialog(int id) {
+ return null;
+ }
+
+ /**
+ * Callback for creating dialogs that are managed (saved and restored) for you
+ * by the activity. The default implementation calls through to
+ * {@link #onCreateDialog(int)} for compatibility.
+ *
+ * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * or later, consider instead using a {@link DialogFragment} instead.</em>
+ *
+ * <p>If you use {@link #showDialog(int)}, the activity will call through to
+ * this method the first time, and hang onto it thereafter. Any dialog
+ * that is created by this method will automatically be saved and restored
+ * for you, including whether it is showing.
+ *
+ * <p>If you would like the activity to manage saving and restoring dialogs
+ * for you, you should override this method and handle any ids that are
+ * passed to {@link #showDialog}.
+ *
+ * <p>If you would like an opportunity to prepare your dialog before it is shown,
+ * override {@link #onPrepareDialog(int, Dialog, Bundle)}.
+ *
+ * @param id The id of the dialog.
+ * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+ * @return The dialog. If you return null, the dialog will not be created.
+ *
+ * @see #onPrepareDialog(int, Dialog, Bundle)
+ * @see #showDialog(int, Bundle)
+ * @see #dismissDialog(int)
+ * @see #removeDialog(int)
+ *
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Nullable
+ @Deprecated
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ return onCreateDialog(id);
+ }
+
+ /**
+ * @deprecated Old no-arguments version of
+ * {@link #onPrepareDialog(int, Dialog, Bundle)}.
+ */
+ @Deprecated
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ dialog.setOwnerActivity(this);
+ }
+
+ /**
+ * Provides an opportunity to prepare a managed dialog before it is being
+ * shown. The default implementation calls through to
+ * {@link #onPrepareDialog(int, Dialog)} for compatibility.
+ *
+ * <p>
+ * Override this if you need to update a managed dialog based on the state
+ * of the application each time it is shown. For example, a time picker
+ * dialog might want to be updated with the current time. You should call
+ * through to the superclass's implementation. The default implementation
+ * will set this Activity as the owner activity on the Dialog.
+ *
+ * @param id The id of the managed dialog.
+ * @param dialog The dialog.
+ * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+ * @see #onCreateDialog(int, Bundle)
+ * @see #showDialog(int)
+ * @see #dismissDialog(int)
+ * @see #removeDialog(int)
+ *
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+ onPrepareDialog(id, dialog);
+ }
+
+ /**
+ * Simple version of {@link #showDialog(int, Bundle)} that does not
+ * take any arguments. Simply calls {@link #showDialog(int, Bundle)}
+ * with null arguments.
+ *
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ public final void showDialog(int id) {
+ showDialog(id, null);
+ }
+
+ /**
+ * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int, Bundle)}
+ * will be made with the same id the first time this is called for a given
+ * id. From thereafter, the dialog will be automatically saved and restored.
+ *
+ * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * or later, consider instead using a {@link DialogFragment} instead.</em>
+ *
+ * <p>Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will
+ * be made to provide an opportunity to do any timely preparation.
+ *
+ * @param id The id of the managed dialog.
+ * @param args Arguments to pass through to the dialog. These will be saved
+ * and restored for you. Note that if the dialog is already created,
+ * {@link #onCreateDialog(int, Bundle)} will not be called with the new
+ * arguments but {@link #onPrepareDialog(int, Dialog, Bundle)} will be.
+ * If you need to rebuild the dialog, call {@link #removeDialog(int)} first.
+ * @return Returns true if the Dialog was created; false is returned if
+ * it is not created because {@link #onCreateDialog(int, Bundle)} returns false.
+ *
+ * @see Dialog
+ * @see #onCreateDialog(int, Bundle)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
+ * @see #dismissDialog(int)
+ * @see #removeDialog(int)
+ *
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ public final boolean showDialog(int id, Bundle args) {
+ if (mManagedDialogs == null) {
+ mManagedDialogs = new SparseArray<ManagedDialog>();
+ }
+ ManagedDialog md = mManagedDialogs.get(id);
+ if (md == null) {
+ md = new ManagedDialog();
+ md.mDialog = createDialog(id, null, args);
+ if (md.mDialog == null) {
+ return false;
+ }
+ mManagedDialogs.put(id, md);
+ }
+
+ md.mArgs = args;
+ onPrepareDialog(id, md.mDialog, args);
+ md.mDialog.show();
+ return true;
+ }
+
+ /**
+ * Dismiss a dialog that was previously shown via {@link #showDialog(int)}.
+ *
+ * @param id The id of the managed dialog.
+ *
+ * @throws IllegalArgumentException if the id was not previously shown via
+ * {@link #showDialog(int)}.
+ *
+ * @see #onCreateDialog(int, Bundle)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
+ * @see #showDialog(int)
+ * @see #removeDialog(int)
+ *
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ public final void dismissDialog(int id) {
+ if (mManagedDialogs == null) {
+ throw missingDialog(id);
+ }
+
+ final ManagedDialog md = mManagedDialogs.get(id);
+ if (md == null) {
+ throw missingDialog(id);
+ }
+ md.mDialog.dismiss();
+ }
+
+ /**
+ * Creates an exception to throw if a user passed in a dialog id that is
+ * unexpected.
+ */
+ private IllegalArgumentException missingDialog(int id) {
+ return new IllegalArgumentException("no dialog with id " + id + " was ever "
+ + "shown via Activity#showDialog");
+ }
+
+ /**
+ * Removes any internal references to a dialog managed by this Activity.
+ * If the dialog is showing, it will dismiss it as part of the clean up.
+ *
+ * <p>This can be useful if you know that you will never show a dialog again and
+ * want to avoid the overhead of saving and restoring it in the future.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function
+ * will not throw an exception if you try to remove an ID that does not
+ * currently have an associated dialog.</p>
+ *
+ * @param id The id of the managed dialog.
+ *
+ * @see #onCreateDialog(int, Bundle)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
+ * @see #showDialog(int)
+ * @see #dismissDialog(int)
+ *
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ */
+ @Deprecated
+ public final void removeDialog(int id) {
+ if (mManagedDialogs != null) {
+ final ManagedDialog md = mManagedDialogs.get(id);
+ if (md != null) {
+ md.mDialog.dismiss();
+ mManagedDialogs.remove(id);
+ }
+ }
+ }
+
+ /**
+ * This hook is called when the user signals the desire to start a search.
+ *
+ * <p>You can use this function as a simple way to launch the search UI, in response to a
+ * menu item, search button, or other widgets within your activity. Unless overidden,
+ * calling this function is the same as calling
+ * {@link #startSearch startSearch(null, false, null, false)}, which launches
+ * search for the current activity as specified in its manifest, see {@link SearchManager}.
+ *
+ * <p>You can override this function to force global search, e.g. in response to a dedicated
+ * search key, or to block search entirely (by simply returning false).
+ *
+ * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_TELEVISION} or
+ * {@link Configuration#UI_MODE_TYPE_WATCH}, the default implementation changes to simply
+ * return false and you must supply your own custom implementation if you want to support
+ * search.
+ *
+ * @param searchEvent The {@link SearchEvent} that signaled this search.
+ * @return Returns {@code true} if search launched, and {@code false} if the activity does
+ * not respond to search. The default implementation always returns {@code true}, except
+ * when in {@link Configuration#UI_MODE_TYPE_TELEVISION} mode where it returns false.
+ *
+ * @see android.app.SearchManager
+ */
+ public boolean onSearchRequested(@Nullable SearchEvent searchEvent) {
+ mSearchEvent = searchEvent;
+ boolean result = onSearchRequested();
+ mSearchEvent = null;
+ return result;
+ }
+
+ /**
+ * @see #onSearchRequested(SearchEvent)
+ */
+ public boolean onSearchRequested() {
+ final int uiMode = getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK;
+ if (uiMode != Configuration.UI_MODE_TYPE_TELEVISION
+ && uiMode != Configuration.UI_MODE_TYPE_WATCH) {
+ startSearch(null, false, null, false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * During the onSearchRequested() callbacks, this function will return the
+ * {@link SearchEvent} that triggered the callback, if it exists.
+ *
+ * @return SearchEvent The SearchEvent that triggered the {@link
+ * #onSearchRequested} callback.
+ */
+ public final SearchEvent getSearchEvent() {
+ return mSearchEvent;
+ }
+
+ /**
+ * This hook is called to launch the search UI.
+ *
+ * <p>It is typically called from onSearchRequested(), either directly from
+ * Activity.onSearchRequested() or from an overridden version in any given
+ * Activity. If your goal is simply to activate search, it is preferred to call
+ * onSearchRequested(), which may have been overridden elsewhere in your Activity. If your goal
+ * is to inject specific data such as context data, it is preferred to <i>override</i>
+ * onSearchRequested(), so that any callers to it will benefit from the override.
+ *
+ * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_WATCH}, use of this API is
+ * not supported.
+ *
+ * @param initialQuery Any non-null non-empty string will be inserted as
+ * pre-entered text in the search query box.
+ * @param selectInitialQuery If true, the initial query will be preselected, which means that
+ * any further typing will replace it. This is useful for cases where an entire pre-formed
+ * query is being inserted. If false, the selection point will be placed at the end of the
+ * inserted query. This is useful when the inserted query is text that the user entered,
+ * and the user would expect to be able to keep typing. <i>This parameter is only meaningful
+ * if initialQuery is a non-empty string.</i>
+ * @param appSearchData An application can insert application-specific
+ * context here, in order to improve quality or specificity of its own
+ * searches. This data will be returned with SEARCH intent(s). Null if
+ * no extra data is required.
+ * @param globalSearch If false, this will only launch the search that has been specifically
+ * defined by the application (which is usually defined as a local search). If no default
+ * search is defined in the current application or activity, global search will be launched.
+ * If true, this will always launch a platform-global (e.g. web-based) search instead.
+ *
+ * @see android.app.SearchManager
+ * @see #onSearchRequested
+ */
+ public void startSearch(@Nullable String initialQuery, boolean selectInitialQuery,
+ @Nullable Bundle appSearchData, boolean globalSearch) {
+ ensureSearchManager();
+ mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ appSearchData, globalSearch);
+ }
+
+ /**
+ * Similar to {@link #startSearch}, but actually fires off the search query after invoking
+ * the search dialog. Made available for testing purposes.
+ *
+ * @param query The query to trigger. If empty, the request will be ignored.
+ * @param appSearchData An application can insert application-specific
+ * context here, in order to improve quality or specificity of its own
+ * searches. This data will be returned with SEARCH intent(s). Null if
+ * no extra data is required.
+ */
+ public void triggerSearch(String query, @Nullable Bundle appSearchData) {
+ ensureSearchManager();
+ mSearchManager.triggerSearch(query, getComponentName(), appSearchData);
+ }
+
+ /**
+ * Request that key events come to this activity. Use this if your
+ * activity has no views with focus, but the activity still wants
+ * a chance to process key events.
+ *
+ * @see android.view.Window#takeKeyEvents
+ */
+ public void takeKeyEvents(boolean get) {
+ getWindow().takeKeyEvents(get);
+ }
+
+ /**
+ * Enable extended window features. This is a convenience for calling
+ * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+ *
+ * @param featureId The desired feature as defined in
+ * {@link android.view.Window}.
+ * @return Returns true if the requested feature is supported and now
+ * enabled.
+ *
+ * @see android.view.Window#requestFeature
+ */
+ public final boolean requestWindowFeature(int featureId) {
+ return getWindow().requestFeature(featureId);
+ }
+
+ /**
+ * Convenience for calling
+ * {@link android.view.Window#setFeatureDrawableResource}.
+ */
+ public final void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
+ getWindow().setFeatureDrawableResource(featureId, resId);
+ }
+
+ /**
+ * Convenience for calling
+ * {@link android.view.Window#setFeatureDrawableUri}.
+ */
+ public final void setFeatureDrawableUri(int featureId, Uri uri) {
+ getWindow().setFeatureDrawableUri(featureId, uri);
+ }
+
+ /**
+ * Convenience for calling
+ * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
+ */
+ public final void setFeatureDrawable(int featureId, Drawable drawable) {
+ getWindow().setFeatureDrawable(featureId, drawable);
+ }
+
+ /**
+ * Convenience for calling
+ * {@link android.view.Window#setFeatureDrawableAlpha}.
+ */
+ public final void setFeatureDrawableAlpha(int featureId, int alpha) {
+ getWindow().setFeatureDrawableAlpha(featureId, alpha);
+ }
+
+ /**
+ * Convenience for calling
+ * {@link android.view.Window#getLayoutInflater}.
+ */
+ @NonNull
+ public LayoutInflater getLayoutInflater() {
+ return getWindow().getLayoutInflater();
+ }
+
+ /**
+ * Returns a {@link MenuInflater} with this context.
+ */
+ @NonNull
+ public MenuInflater getMenuInflater() {
+ // Make sure that action views can get an appropriate theme.
+ if (mMenuInflater == null) {
+ initWindowDecorActionBar();
+ if (mActionBar != null) {
+ mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this);
+ } else {
+ mMenuInflater = new MenuInflater(this);
+ }
+ }
+ return mMenuInflater;
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ super.setTheme(resid);
+ mWindow.setTheme(resid);
+ }
+
+ @Override
+ protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid,
+ boolean first) {
+ if (mParent == null) {
+ super.onApplyThemeResource(theme, resid, first);
+ } else {
+ try {
+ theme.setTo(mParent.getTheme());
+ } catch (Exception e) {
+ // Empty
+ }
+ theme.applyStyle(resid, false);
+ }
+
+ // Get the primary color and update the TaskDescription for this activity
+ TypedArray a = theme.obtainStyledAttributes(
+ com.android.internal.R.styleable.ActivityTaskDescription);
+ if (mTaskDescription.getPrimaryColor() == 0) {
+ int colorPrimary = a.getColor(
+ com.android.internal.R.styleable.ActivityTaskDescription_colorPrimary, 0);
+ if (colorPrimary != 0 && Color.alpha(colorPrimary) == 0xFF) {
+ mTaskDescription.setPrimaryColor(colorPrimary);
+ }
+ }
+
+ int colorBackground = a.getColor(
+ com.android.internal.R.styleable.ActivityTaskDescription_colorBackground, 0);
+ if (colorBackground != 0 && Color.alpha(colorBackground) == 0xFF) {
+ mTaskDescription.setBackgroundColor(colorBackground);
+ }
+
+ int colorBackgroundFloating = a.getColor(
+ com.android.internal.R.styleable.ActivityTaskDescription_colorBackgroundFloating,
+ 0);
+ if (colorBackgroundFloating != 0 && Color.alpha(colorBackgroundFloating) == 0xFF) {
+ mTaskDescription.setBackgroundColorFloating(colorBackgroundFloating);
+ }
+
+ final int statusBarColor = a.getColor(
+ com.android.internal.R.styleable.ActivityTaskDescription_statusBarColor, 0);
+ if (statusBarColor != 0) {
+ mTaskDescription.setStatusBarColor(statusBarColor);
+ }
+
+ final int navigationBarColor = a.getColor(
+ com.android.internal.R.styleable.ActivityTaskDescription_navigationBarColor, 0);
+ if (navigationBarColor != 0) {
+ mTaskDescription.setNavigationBarColor(navigationBarColor);
+ }
+
+ final int targetSdk = getApplicationInfo().targetSdkVersion;
+ final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
+ if (!targetPreQ) {
+ mTaskDescription.setEnsureStatusBarContrastWhenTransparent(a.getBoolean(
+ R.styleable.ActivityTaskDescription_enforceStatusBarContrast,
+ false));
+ mTaskDescription.setEnsureNavigationBarContrastWhenTransparent(a.getBoolean(
+ R.styleable
+ .ActivityTaskDescription_enforceNavigationBarContrast,
+ true));
+ }
+
+ a.recycle();
+ setTaskDescription(mTaskDescription);
+ }
+
+ /**
+ * Requests permissions to be granted to this application. These permissions
+ * must be requested in your manifest, they should not be granted to your app,
+ * and they should have protection level {@link
+ * android.content.pm.PermissionInfo#PROTECTION_DANGEROUS dangerous}, regardless
+ * whether they are declared by the platform or a third-party app.
+ * <p>
+ * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL}
+ * are granted at install time if requested in the manifest. Signature permissions
+ * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at
+ * install time if requested in the manifest and the signature of your app matches
+ * the signature of the app declaring the permissions.
+ * </p>
+ * <p>
+ * Call {@link #shouldShowRequestPermissionRationale(String)} before calling this API to
+ * check if the system recommends to show a rationale UI before asking for a permission.
+ * </p>
+ * <p>
+ * If your app does not have the requested permissions the user will be presented
+ * with UI for accepting them. After the user has accepted or rejected the
+ * requested permissions you will receive a callback on {@link
+ * #onRequestPermissionsResult(int, String[], int[])} reporting whether the
+ * permissions were granted or not.
+ * </p>
+ * <p>
+ * Note that requesting a permission does not guarantee it will be granted and
+ * your app should be able to run without having this permission.
+ * </p>
+ * <p>
+ * This method may start an activity allowing the user to choose which permissions
+ * to grant and which to reject. Hence, you should be prepared that your activity
+ * may be paused and resumed. Further, granting some permissions may require
+ * a restart of you application. In such a case, the system will recreate the
+ * activity stack before delivering the result to {@link
+ * #onRequestPermissionsResult(int, String[], int[])}.
+ * </p>
+ * <p>
+ * When checking whether you have a permission you should use {@link
+ * #checkSelfPermission(String)}.
+ * </p>
+ * <p>
+ * You cannot request a permission if your activity sets {@link
+ * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
+ * <code>true</code> because in this case the activity would not receive
+ * result callbacks including {@link #onRequestPermissionsResult(int, String[], int[])}.
+ * </p>
+ * <p>
+ * The <a href="https://github.com/android/permissions-samples">
+ * RuntimePermissions</a> sample apps demonstrate how to use this method to
+ * request permissions at run time.
+ * </p>
+ *
+ * @param permissions The requested permissions. Must me non-null and not empty.
+ * @param requestCode Application specific request code to match with a result
+ * reported to {@link #onRequestPermissionsResult(int, String[], int[])}.
+ * Should be >= 0.
+ *
+ * @throws IllegalArgumentException if requestCode is negative.
+ *
+ * @see #onRequestPermissionsResult(int, String[], int[])
+ * @see #checkSelfPermission(String)
+ * @see #shouldShowRequestPermissionRationale(String)
+ */
+ public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
+ if (requestCode < 0) {
+ throw new IllegalArgumentException("requestCode should be >= 0");
+ }
+
+ if (mHasCurrentPermissionsRequest) {
+ Log.w(TAG, "Can request only one set of permissions at a time");
+ // Dispatch the callback with empty arrays which means a cancellation.
+ onRequestPermissionsResult(requestCode, new String[0], new int[0]);
+ return;
+ }
+
+ if (!getAttributionSource().getRenouncedPermissions().isEmpty()) {
+ final int permissionCount = permissions.length;
+ for (int i = 0; i < permissionCount; i++) {
+ if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) {
+ throw new IllegalArgumentException("Cannot request renounced permission: "
+ + permissions[i]);
+ }
+ }
+ }
+
+ final Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+ startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
+ mHasCurrentPermissionsRequest = true;
+ }
+
+ /**
+ * Callback for the result from requesting permissions. This method
+ * is invoked for every call on {@link #requestPermissions(String[], int)}.
+ * <p>
+ * <strong>Note:</strong> It is possible that the permissions request interaction
+ * with the user is interrupted. In this case you will receive empty permissions
+ * and results arrays which should be treated as a cancellation.
+ * </p>
+ *
+ * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
+ * @param permissions The requested permissions. Never null.
+ * @param grantResults The grant results for the corresponding permissions
+ * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
+ * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+ *
+ * @see #requestPermissions(String[], int)
+ */
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ /* callback - no nothing */
+ }
+
+ /**
+ * Gets whether you should show UI with rationale before requesting a permission.
+ *
+ * @param permission A permission your app wants to request.
+ * @return Whether you should show permission rationale UI.
+ *
+ * @see #checkSelfPermission(String)
+ * @see #requestPermissions(String[], int)
+ * @see #onRequestPermissionsResult(int, String[], int[])
+ */
+ public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
+ return getPackageManager().shouldShowRequestPermissionRationale(permission);
+ }
+
+ /**
+ * Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity
+ */
+ public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode, null);
+ }
+
+ /**
+ * Launch an activity for which you would like a result when it finished.
+ * When this activity exits, your
+ * onActivityResult() method will be called with the given requestCode.
+ * Using a negative requestCode is the same as calling
+ * {@link #startActivity} (the activity is not launched as a sub-activity).
+ *
+ * <p>Note that this method should only be used with Intent protocols
+ * that are defined to return a result. In other protocols (such as
+ * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+ * not get the result when you expect. For example, if the activity you
+ * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+ * run in your task and thus you will immediately receive a cancel result.
+ *
+ * <p>As a special case, if you call startActivityForResult() with a requestCode
+ * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+ * activity, then your window will not be displayed until a result is
+ * returned back from the started activity. This is to avoid visible
+ * flickering when redirecting to another activity.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity
+ */
+ public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
+ @Nullable Bundle options) {
+ if (mParent == null) {
+ options = transferSpringboardActivityOptions(options);
+ Instrumentation.ActivityResult ar =
+ mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, this,
+ intent, requestCode, options);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, mEmbeddedID, requestCode, ar.getResultCode(),
+ ar.getResultData());
+ }
+ if (requestCode >= 0) {
+ // If this start is requesting a result, we can avoid making
+ // the activity visible until the result is received. Setting
+ // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+ // activity hidden during this time, to avoid flickering.
+ // This can only be done when a result is requested because
+ // that guarantees we will get information back when the
+ // activity is finished, no matter what happens to it.
+ mStartedActivity = true;
+ }
+
+ cancelInputsAndStartExitTransition(options);
+ // TODO Consider clearing/flushing other event sources and events for child windows.
+ } else {
+ if (options != null) {
+ mParent.startActivityFromChild(this, intent, requestCode, options);
+ } else {
+ // Note we want to go through this method for compatibility with
+ // existing applications that may have overridden it.
+ mParent.startActivityFromChild(this, intent, requestCode);
+ }
+ }
+ }
+
+ /**
+ * Cancels pending inputs and if an Activity Transition is to be run, starts the transition.
+ *
+ * @param options The ActivityOptions bundle used to start an Activity.
+ */
+ private void cancelInputsAndStartExitTransition(Bundle options) {
+ final View decor = mWindow != null ? mWindow.peekDecorView() : null;
+ if (decor != null) {
+ decor.cancelPendingInputEvents();
+ }
+ if (options != null) {
+ mActivityTransitionState.startExitOutTransition(this, options);
+ }
+ }
+
+ /**
+ * Returns whether there are any activity transitions currently running on this
+ * activity. A return value of {@code true} can mean that either an enter or
+ * exit transition is running, including whether the background of the activity
+ * is animating as a part of that transition.
+ *
+ * @return true if a transition is currently running on this activity, false otherwise.
+ */
+ public boolean isActivityTransitionRunning() {
+ return mActivityTransitionState.isTransitionRunning();
+ }
+
+ private Bundle transferSpringboardActivityOptions(@Nullable Bundle options) {
+ if (options == null && (mWindow != null && !mWindow.isActive())) {
+ final ActivityOptions activityOptions = getActivityOptions();
+ if (activityOptions != null &&
+ activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ return activityOptions.toBundle();
+ }
+ }
+ return options;
+ }
+
+ /**
+ * Launch an activity for which you would like a result when it finished.
+ * When this activity exits, your
+ * onActivityResult() method will be called with the given requestCode.
+ * Using a negative requestCode is the same as calling
+ * {@link #startActivity} (the activity is not launched as a sub-activity).
+ *
+ * <p>Note that this method should only be used with Intent protocols
+ * that are defined to return a result. In other protocols (such as
+ * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+ * not get the result when you expect. For example, if the activity you
+ * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+ * run in your task and thus you will immediately receive a cancel result.
+ *
+ * <p>As a special case, if you call startActivityForResult() with a requestCode
+ * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+ * activity, then your window will not be displayed until a result is
+ * returned back from the started activity. This is to avoid visible
+ * flickering when redirecting to another activity.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param user The user to start the intent as.
+ * @hide Implement to provide correct calling token.
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+ public void startActivityForResultAsUser(@NonNull Intent intent, int requestCode,
+ @NonNull UserHandle user) {
+ startActivityForResultAsUser(intent, requestCode, null, user);
+ }
+
+ /**
+ * Launch an activity for which you would like a result when it finished.
+ * When this activity exits, your
+ * onActivityResult() method will be called with the given requestCode.
+ * Using a negative requestCode is the same as calling
+ * {@link #startActivity} (the activity is not launched as a sub-activity).
+ *
+ * <p>Note that this method should only be used with Intent protocols
+ * that are defined to return a result. In other protocols (such as
+ * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+ * not get the result when you expect. For example, if the activity you
+ * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+ * run in your task and thus you will immediately receive a cancel result.
+ *
+ * <p>As a special case, if you call startActivityForResult() with a requestCode
+ * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+ * activity, then your window will not be displayed until a result is
+ * returned back from the started activity. This is to avoid visible
+ * flickering when redirecting to another activity.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param options Additional options for how the Activity should be started. See {@link
+ * android.content.Context#startActivity(Intent, Bundle)} for more details.
+ * @param user The user to start the intent as.
+ * @hide Implement to provide correct calling token.
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+ public void startActivityForResultAsUser(@NonNull Intent intent, int requestCode,
+ @Nullable Bundle options, @NonNull UserHandle user) {
+ startActivityForResultAsUser(intent, mEmbeddedID, requestCode, options, user);
+ }
+
+ /**
+ * Launch an activity for which you would like a result when it finished.
+ * When this activity exits, your
+ * onActivityResult() method will be called with the given requestCode.
+ * Using a negative requestCode is the same as calling
+ * {@link #startActivity} (the activity is not launched as a sub-activity).
+ *
+ * <p>Note that this method should only be used with Intent protocols
+ * that are defined to return a result. In other protocols (such as
+ * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+ * not get the result when you expect. For example, if the activity you
+ * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+ * run in your task and thus you will immediately receive a cancel result.
+ *
+ * <p>As a special case, if you call startActivityForResult() with a requestCode
+ * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+ * activity, then your window will not be displayed until a result is
+ * returned back from the started activity. This is to avoid visible
+ * flickering when redirecting to another activity.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param options Additional options for how the Activity should be started. See {@link
+ * android.content.Context#startActivity(Intent, Bundle)} for more details.
+ * @param user The user to start the intent as.
+ * @hide Implement to provide correct calling token.
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+ public void startActivityForResultAsUser(@NonNull Intent intent, @NonNull String resultWho,
+ int requestCode,
+ @Nullable Bundle options, @NonNull UserHandle user) {
+ if (mParent != null) {
+ throw new RuntimeException("Can't be called from a child");
+ }
+ options = transferSpringboardActivityOptions(options);
+ Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, resultWho, intent, requestCode,
+ options, user);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+ }
+ if (requestCode >= 0) {
+ // If this start is requesting a result, we can avoid making
+ // the activity visible until the result is received. Setting
+ // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+ // activity hidden during this time, to avoid flickering.
+ // This can only be done when a result is requested because
+ // that guarantees we will get information back when the
+ // activity is finished, no matter what happens to it.
+ mStartedActivity = true;
+ }
+
+ cancelInputsAndStartExitTransition(options);
+ }
+
+ /**
+ * @hide Implement to provide correct calling token.
+ */
+ @Override
+ public void startActivityAsUser(Intent intent, UserHandle user) {
+ startActivityAsUser(intent, null, user);
+ }
+
+ /**
+ * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the
+ * user the activity will be started for. This is not available to applications
+ * that are not pre-installed on the system image.
+ * @param intent The description of the activity to start.
+ *
+ * @param user The UserHandle of the user to start this activity for.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ * @throws ActivityNotFoundException
+ * @hide
+ */
+ @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+ public void startActivityAsUser(@NonNull Intent intent,
+ @Nullable Bundle options, @NonNull UserHandle user) {
+ if (mParent != null) {
+ throw new RuntimeException("Can't be called from a child");
+ }
+ options = transferSpringboardActivityOptions(options);
+ Instrumentation.ActivityResult ar =
+ mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, mEmbeddedID,
+ intent, -1, options, user);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, mEmbeddedID, -1, ar.getResultCode(),
+ ar.getResultData());
+ }
+ cancelInputsAndStartExitTransition(options);
+ }
+
+ /**
+ * Start a new activity as if it was started by the activity that started our
+ * current activity. This is for the resolver and chooser activities, which operate
+ * as intermediaries that dispatch their intent to the target the user selects -- to
+ * do this, they must perform all security checks including permission grants as if
+ * their launch had come from the original activity.
+ * @param intent The Intent to start.
+ * @param options ActivityOptions or null.
+ * @param ignoreTargetSecurity If true, the activity manager will not check whether the
+ * caller it is doing the start is, is actually allowed to start the target activity.
+ * If you set this to true, you must set an explicit component in the Intent and do any
+ * appropriate security checks yourself.
+ * @param userId The user the new activity should run as.
+ * @hide
+ */
+ public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+ boolean ignoreTargetSecurity, int userId) {
+ startActivityAsCaller(intent, options, ignoreTargetSecurity, userId, -1);
+ }
+
+ /**
+ * @see #startActivityAsCaller(Intent, Bundle, boolean, int)
+ * @param requestCode The request code used for returning a result or -1 if no result should be
+ * returned.
+ * @hide
+ */
+ public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+ boolean ignoreTargetSecurity, int userId, int requestCode) {
+ if (mParent != null) {
+ throw new RuntimeException("Can't be called from a child");
+ }
+ options = transferSpringboardActivityOptions(options);
+ Instrumentation.ActivityResult ar =
+ mInstrumentation.execStartActivityAsCaller(
+ this, mMainThread.getApplicationThread(), mToken, this,
+ intent, requestCode, options, ignoreTargetSecurity, userId);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+ }
+ cancelInputsAndStartExitTransition(options);
+ }
+
+ /**
+ * Same as calling {@link #startIntentSenderForResult(IntentSender, int,
+ * Intent, int, int, int, Bundle)} with no options.
+ *
+ * @param intent The IntentSender to launch.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ */
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, null);
+ }
+
+ /**
+ * Like {@link #startIntentSenderForResult} but taking {@code who} as an additional identifier.
+ *
+ * @hide
+ */
+ public void startIntentSenderForResult(IntentSender intent, String who, int requestCode,
+ Intent fillInIntent, int flagsMask, int flagsValues, Bundle options)
+ throws IntentSender.SendIntentException {
+ startIntentSenderForResultInner(intent, who, requestCode, fillInIntent, flagsMask,
+ flagsValues, options);
+ }
+
+ /**
+ * Like {@link #startActivityForResult(Intent, int)}, but allowing you
+ * to use a IntentSender to describe the activity to be started. If
+ * the IntentSender is for an activity, that activity will be started
+ * as if you had called the regular {@link #startActivityForResult(Intent, int)}
+ * here; otherwise, its associated action will be executed (such as
+ * sending a broadcast) as if you had called
+ * {@link IntentSender#sendIntent IntentSender.sendIntent} on it.
+ *
+ * @param intent The IntentSender to launch.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details. If options
+ * have also been supplied by the IntentSender, options given here will
+ * override any that conflict with those given by the IntentSender.
+ */
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Bundle options) throws IntentSender.SendIntentException {
+ if (mParent == null) {
+ startIntentSenderForResultInner(intent, mEmbeddedID, requestCode, fillInIntent,
+ flagsMask, flagsValues, options);
+ } else if (options != null) {
+ mParent.startIntentSenderFromChild(this, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // existing applications that may have overridden the method.
+ mParent.startIntentSenderFromChild(this, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void startIntentSenderForResultInner(IntentSender intent, String who, int requestCode,
+ Intent fillInIntent, int flagsMask, int flagsValues,
+ @Nullable Bundle options)
+ throws IntentSender.SendIntentException {
+ try {
+ options = transferSpringboardActivityOptions(options);
+ String resolvedType = null;
+ if (fillInIntent != null) {
+ fillInIntent.migrateExtraStreamToClipData(this);
+ fillInIntent.prepareToLeaveProcess(this);
+ resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver());
+ }
+ int result = ActivityTaskManager.getService()
+ .startActivityIntentSender(mMainThread.getApplicationThread(),
+ intent != null ? intent.getTarget() : null,
+ intent != null ? intent.getWhitelistToken() : null,
+ fillInIntent, resolvedType, mToken, who,
+ requestCode, flagsMask, flagsValues, options);
+ if (result == ActivityManager.START_CANCELED) {
+ throw new IntentSender.SendIntentException();
+ }
+ Instrumentation.checkStartActivityResult(result, null);
+
+ if (options != null) {
+ // Only when the options are not null, as the intent can point to something other
+ // than an Activity.
+ cancelInputsAndStartExitTransition(options);
+ }
+ } catch (RemoteException e) {
+ }
+ if (requestCode >= 0) {
+ // If this start is requesting a result, we can avoid making
+ // the activity visible until the result is received. Setting
+ // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+ // activity hidden during this time, to avoid flickering.
+ // This can only be done when a result is requested because
+ // that guarantees we will get information back when the
+ // activity is finished, no matter what happens to it.
+ mStartedActivity = true;
+ }
+ }
+
+ /**
+ * Same as {@link #startActivity(Intent, Bundle)} with no options
+ * specified.
+ *
+ * @param intent The intent to start.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity(Intent, Bundle)
+ * @see #startActivityForResult
+ */
+ @Override
+ public void startActivity(Intent intent) {
+ this.startActivity(intent, null);
+ }
+
+ /**
+ * Launch a new activity. You will not receive any information about when
+ * the activity exits. This implementation overrides the base version,
+ * providing information about
+ * the activity performing the launch. Because of this additional
+ * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
+ * required; if not specified, the new activity will be added to the
+ * task of the caller.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intent The intent to start.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity(Intent)
+ * @see #startActivityForResult
+ */
+ @Override
+ public void startActivity(Intent intent, @Nullable Bundle options) {
+ getAutofillClientController().onStartActivity(intent, mIntent);
+ if (options != null) {
+ startActivityForResult(intent, -1, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // applications that may have overridden the method.
+ startActivityForResult(intent, -1);
+ }
+ }
+
+ /**
+ * Same as {@link #startActivities(Intent[], Bundle)} with no options
+ * specified.
+ *
+ * @param intents The intents to start.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivities(Intent[], Bundle)
+ * @see #startActivityForResult
+ */
+ @Override
+ public void startActivities(Intent[] intents) {
+ startActivities(intents, null);
+ }
+
+ /**
+ * Launch a new activity. You will not receive any information about when
+ * the activity exits. This implementation overrides the base version,
+ * providing information about
+ * the activity performing the launch. Because of this additional
+ * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
+ * required; if not specified, the new activity will be added to the
+ * task of the caller.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param intents The intents to start.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivities(Intent[])
+ * @see #startActivityForResult
+ */
+ @Override
+ public void startActivities(Intent[] intents, @Nullable Bundle options) {
+ mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(),
+ mToken, this, intents, options);
+ }
+
+ /**
+ * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+ * with no options.
+ *
+ * @param intent The IntentSender to launch.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ */
+ @Override
+ public void startIntentSender(IntentSender intent,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ startIntentSender(intent, fillInIntent, flagsMask, flagsValues,
+ extraFlags, null);
+ }
+
+ /**
+ * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
+ * to start; see
+ * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)}
+ * for more information.
+ *
+ * @param intent The IntentSender to launch.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details. If options
+ * have also been supplied by the IntentSender, options given here will
+ * override any that conflict with those given by the IntentSender.
+ */
+ @Override
+ public void startIntentSender(IntentSender intent,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Bundle options) throws IntentSender.SendIntentException {
+ if (options != null) {
+ startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // applications that may have overridden the method.
+ startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+ flagsValues, extraFlags);
+ }
+ }
+
+ /**
+ * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits, as described in
+ * {@link #startActivityForResult}.
+ *
+ * @return If a new activity was launched then true is returned; otherwise
+ * false is returned and you must handle the Intent yourself.
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
+ */
+ public boolean startActivityIfNeeded(@RequiresPermission @NonNull Intent intent,
+ int requestCode) {
+ return startActivityIfNeeded(intent, requestCode, null);
+ }
+
+ /**
+ * A special variation to launch an activity only if a new activity
+ * instance is needed to handle the given Intent. In other words, this is
+ * just like {@link #startActivityForResult(Intent, int)} except: if you are
+ * using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or
+ * singleTask or singleTop
+ * {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode},
+ * and the activity
+ * that handles <var>intent</var> is the same as your currently running
+ * activity, then a new instance is not needed. In this case, instead of
+ * the normal behavior of calling {@link #onNewIntent} this function will
+ * return and you can handle the Intent yourself.
+ *
+ * <p>This function can only be called from a top-level activity; if it is
+ * called from a child activity, a runtime exception will be thrown.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits, as described in
+ * {@link #startActivityForResult}.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @return If a new activity was launched then true is returned; otherwise
+ * false is returned and you must handle the Intent yourself.
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
+ */
+ public boolean startActivityIfNeeded(@RequiresPermission @NonNull Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ if (mParent == null) {
+ int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
+ try {
+ Uri referrer = onProvideReferrer();
+ if (referrer != null) {
+ intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+ }
+ intent.migrateExtraStreamToClipData(this);
+ intent.prepareToLeaveProcess(this);
+ result = ActivityTaskManager.getService()
+ .startActivity(mMainThread.getApplicationThread(), getOpPackageName(),
+ getAttributionTag(), intent,
+ intent.resolveTypeIfNeeded(getContentResolver()), mToken, mEmbeddedID,
+ requestCode, ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, options);
+ } catch (RemoteException e) {
+ // Empty
+ }
+
+ Instrumentation.checkStartActivityResult(result, intent);
+
+ if (requestCode >= 0) {
+ // If this start is requesting a result, we can avoid making
+ // the activity visible until the result is received. Setting
+ // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+ // activity hidden during this time, to avoid flickering.
+ // This can only be done when a result is requested because
+ // that guarantees we will get information back when the
+ // activity is finished, no matter what happens to it.
+ mStartedActivity = true;
+ }
+ return result != ActivityManager.START_RETURN_INTENT_TO_CALLER;
+ }
+
+ throw new UnsupportedOperationException(
+ "startActivityIfNeeded can only be called from a top-level activity");
+ }
+
+ /**
+ * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with
+ * no options.
+ *
+ * @param intent The intent to dispatch to the next activity. For
+ * correct behavior, this must be the same as the Intent that started
+ * your own activity; the only changes you can make are to the extras
+ * inside of it.
+ *
+ * @return Returns a boolean indicating whether there was another Activity
+ * to start: true if there was a next activity to start, false if there
+ * wasn't. In general, if true is returned you will then want to call
+ * finish() on yourself.
+ */
+ public boolean startNextMatchingActivity(@RequiresPermission @NonNull Intent intent) {
+ return startNextMatchingActivity(intent, null);
+ }
+
+ /**
+ * Special version of starting an activity, for use when you are replacing
+ * other activity components. You can use this to hand the Intent off
+ * to the next Activity that can handle it. You typically call this in
+ * {@link #onCreate} with the Intent returned by {@link #getIntent}.
+ *
+ * @param intent The intent to dispatch to the next activity. For
+ * correct behavior, this must be the same as the Intent that started
+ * your own activity; the only changes you can make are to the extras
+ * inside of it.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @return Returns a boolean indicating whether there was another Activity
+ * to start: true if there was a next activity to start, false if there
+ * wasn't. In general, if true is returned you will then want to call
+ * finish() on yourself.
+ */
+ public boolean startNextMatchingActivity(@RequiresPermission @NonNull Intent intent,
+ @Nullable Bundle options) {
+ if (mParent == null) {
+ try {
+ intent.migrateExtraStreamToClipData(this);
+ intent.prepareToLeaveProcess(this);
+ return ActivityTaskManager.getService()
+ .startNextMatchingActivity(mToken, intent, options);
+ } catch (RemoteException e) {
+ // Empty
+ }
+ return false;
+ }
+
+ throw new UnsupportedOperationException(
+ "startNextMatchingActivity can only be called from a top-level activity");
+ }
+
+ /**
+ * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param child The activity making the call.
+ * @param intent The intent to start.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
+ * @deprecated Use {@code androidx.fragment.app.FragmentActivity#startActivityFromFragment(
+ * androidx.fragment.app.Fragment,Intent,int)}
+ */
+ @Deprecated
+ public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
+ int requestCode) {
+ startActivityFromChild(child, intent, requestCode, null);
+ }
+
+ /**
+ * This is called when a child activity of this one calls its
+ * {@link #startActivity} or {@link #startActivityForResult} method.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param child The activity making the call.
+ * @param intent The intent to start.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
+ * @deprecated Use {@code androidx.fragment.app.FragmentActivity#startActivityFromFragment(
+ * androidx.fragment.app.Fragment,Intent,int,Bundle)}
+ */
+ @Deprecated
+ public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ options = transferSpringboardActivityOptions(options);
+ Instrumentation.ActivityResult ar =
+ mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, child,
+ intent, requestCode, options);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, child.mEmbeddedID, requestCode,
+ ar.getResultCode(), ar.getResultData());
+ }
+ cancelInputsAndStartExitTransition(options);
+ }
+
+ /**
+ * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param fragment The fragment making the call.
+ * @param intent The intent to start.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see Fragment#startActivity
+ * @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@code androidx.fragment.app.FragmentActivity#startActivityFromFragment(
+ * androidx.fragment.app.Fragment,Intent,int)}
+ */
+ @Deprecated
+ public void startActivityFromFragment(@NonNull Fragment fragment,
+ @RequiresPermission Intent intent, int requestCode) {
+ startActivityFromFragment(fragment, intent, requestCode, null);
+ }
+
+ /**
+ * This is called when a Fragment in this activity calls its
+ * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
+ * method.
+ *
+ * <p>This method throws {@link android.content.ActivityNotFoundException}
+ * if there was no Activity found to run the given Intent.
+ *
+ * @param fragment The fragment making the call.
+ * @param intent The intent to start.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)}
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see Fragment#startActivity
+ * @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@code androidx.fragment.app.FragmentActivity#startActivityFromFragment(
+ * androidx.fragment.app.Fragment,Intent,int,Bundle)}
+ */
+ @Deprecated
+ public void startActivityFromFragment(@NonNull Fragment fragment,
+ @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
+ startActivityForResult(fragment.mWho, intent, requestCode, options);
+ }
+
+ private void startActivityAsUserFromFragment(@NonNull Fragment fragment,
+ @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options,
+ UserHandle user) {
+ startActivityForResultAsUser(intent, fragment.mWho, requestCode, options, user);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void startActivityForResult(
+ String who, Intent intent, int requestCode, @Nullable Bundle options) {
+ Uri referrer = onProvideReferrer();
+ if (referrer != null) {
+ intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+ }
+ options = transferSpringboardActivityOptions(options);
+ Instrumentation.ActivityResult ar =
+ mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, who,
+ intent, requestCode, options);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, who, requestCode,
+ ar.getResultCode(), ar.getResultData());
+ }
+ cancelInputsAndStartExitTransition(options);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean canStartActivityForResult() {
+ return true;
+ }
+
+ /**
+ * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender,
+ * int, Intent, int, int, int, Bundle)} with no options.
+ * @deprecated Use {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
+ * instead.
+ */
+ @Deprecated
+ public void startIntentSenderFromChild(Activity child, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags)
+ throws IntentSender.SendIntentException {
+ startIntentSenderFromChild(child, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, null);
+ }
+
+ /**
+ * Like {@link #startActivityFromChild(Activity, Intent, int)}, but
+ * taking a IntentSender; see
+ * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
+ * for more information.
+ * @deprecated Use
+ * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)}
+ * instead.
+ */
+ @Deprecated
+ public void startIntentSenderFromChild(Activity child, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, @Nullable Bundle options)
+ throws IntentSender.SendIntentException {
+ startIntentSenderForResultInner(intent, child.mEmbeddedID, requestCode, fillInIntent,
+ flagsMask, flagsValues, options);
+ }
+
+ /**
+ * Like {@link #startIntentSender}, but taking a Fragment; see
+ * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
+ * for more information.
+ */
+ private void startIntentSenderFromFragment(Fragment fragment, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+ @Nullable Bundle options)
+ throws IntentSender.SendIntentException {
+ startIntentSenderForResultInner(intent, fragment.mWho, requestCode, fillInIntent,
+ flagsMask, flagsValues, options);
+ }
+
+ /**
+ * Customizes the animation for the activity transition with this activity. This can be called
+ * at any time while the activity still alive.
+ *
+ * <p> This is a more robust method of overriding the transition animation at runtime without
+ * relying on {@link #overridePendingTransition(int, int)} which doesn't work for predictive
+ * back. However, the animation set from {@link #overridePendingTransition(int, int)} still
+ * has higher priority when the system is looking for the next transition animation.</p>
+ * <p> The animations resources set by this method will be chosen if and only if the activity is
+ * on top of the task while activity transitions are being played.
+ * For example, if we want to customize the opening transition when launching Activity B which
+ * gets started from Activity A, we should call this method inside B's onCreate with
+ * {@code overrideType = OVERRIDE_TRANSITION_OPEN} because the Activity B will on top of the
+ * task. And if we want to customize the closing transition when finishing Activity B and back
+ * to Activity A, since B is still is above A, we should call this method in Activity B with
+ * {@code overrideType = OVERRIDE_TRANSITION_CLOSE}. </p>
+ *
+ * <p> If an Activity has called this method, and it also set another activity animation
+ * by {@link Window#setWindowAnimations(int)}, the system will choose the animation set from
+ * this method.</p>
+ *
+ * <p> Note that {@link Window#setWindowAnimations},
+ * {@link #overridePendingTransition(int, int)} and this method will be ignored if the Activity
+ * is started with {@link ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])}. Also
+ * note that this method can only be used to customize cross-activity transitions but not
+ * cross-task transitions which are fully non-customizable as of Android 11.</p>
+ *
+ * @param overrideType {@code OVERRIDE_TRANSITION_OPEN} This animation will be used when
+ * starting/entering an activity. {@code OVERRIDE_TRANSITION_CLOSE} This
+ * animation will be used when finishing/closing an activity.
+ * @param enterAnim A resource ID of the animation resource to use for the incoming activity.
+ * Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for the outgoing activity.
+ * Use 0 for no animation.
+ *
+ * @see #overrideActivityTransition(int, int, int, int)
+ * @see #clearOverrideActivityTransition(int)
+ * @see OnBackInvokedCallback
+ * @see #overridePendingTransition(int, int)
+ * @see Window#setWindowAnimations(int)
+ * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
+ */
+ public void overrideActivityTransition(@OverrideTransition int overrideType,
+ @AnimRes int enterAnim, @AnimRes int exitAnim) {
+ overrideActivityTransition(overrideType, enterAnim, exitAnim, Color.TRANSPARENT);
+ }
+
+ /**
+ * Customizes the animation for the activity transition with this activity. This can be called
+ * at any time while the activity still alive.
+ *
+ * <p> This is a more robust method of overriding the transition animation at runtime without
+ * relying on {@link #overridePendingTransition(int, int)} which doesn't work for predictive
+ * back. However, the animation set from {@link #overridePendingTransition(int, int)} still
+ * has higher priority when the system is looking for the next transition animation.</p>
+ * <p> The animations resources set by this method will be chosen if and only if the activity is
+ * on top of the task while activity transitions are being played.
+ * For example, if we want to customize the opening transition when launching Activity B which
+ * gets started from Activity A, we should call this method inside B's onCreate with
+ * {@code overrideType = OVERRIDE_TRANSITION_OPEN} because the Activity B will on top of the
+ * task. And if we want to customize the closing transition when finishing Activity B and back
+ * to Activity A, since B is still is above A, we should call this method in Activity B with
+ * {@code overrideType = OVERRIDE_TRANSITION_CLOSE}. </p>
+ *
+ * <p> If an Activity has called this method, and it also set another activity animation
+ * by {@link Window#setWindowAnimations(int)}, the system will choose the animation set from
+ * this method.</p>
+ *
+ * <p> Note that {@link Window#setWindowAnimations},
+ * {@link #overridePendingTransition(int, int)} and this method will be ignored if the Activity
+ * is started with {@link ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])}. Also
+ * note that this method can only be used to customize cross-activity transitions but not
+ * cross-task transitions which are fully non-customizable as of Android 11.</p>
+ *
+ * @param overrideType {@code OVERRIDE_TRANSITION_OPEN} This animation will be used when
+ * starting/entering an activity. {@code OVERRIDE_TRANSITION_CLOSE} This
+ * animation will be used when finishing/closing an activity.
+ * @param enterAnim A resource ID of the animation resource to use for the incoming activity.
+ * Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for the outgoing activity.
+ * Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation
+ * if the animation requires a background. Set to
+ * {@link Color#TRANSPARENT} to not override the default color.
+ * @see #overrideActivityTransition(int, int, int)
+ * @see #clearOverrideActivityTransition(int)
+ * @see OnBackInvokedCallback
+ * @see #overridePendingTransition(int, int)
+ * @see Window#setWindowAnimations(int)
+ * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
+ */
+ public void overrideActivityTransition(@OverrideTransition int overrideType,
+ @AnimRes int enterAnim, @AnimRes int exitAnim, @ColorInt int backgroundColor) {
+ if (overrideType != OVERRIDE_TRANSITION_OPEN && overrideType != OVERRIDE_TRANSITION_CLOSE) {
+ throw new IllegalArgumentException("Override type must be either open or close");
+ }
+
+ ActivityClient.getInstance().overrideActivityTransition(mToken,
+ overrideType == OVERRIDE_TRANSITION_OPEN, enterAnim, exitAnim, backgroundColor);
+ }
+
+ /**
+ * Clears the animations which are set from {@link #overrideActivityTransition}.
+ * @param overrideType {@code OVERRIDE_TRANSITION_OPEN} clear the animation set for starting a
+ * new activity. {@code OVERRIDE_TRANSITION_CLOSE} clear the animation set
+ * for finishing an activity.
+ *
+ * @see #overrideActivityTransition(int, int, int)
+ * @see #overrideActivityTransition(int, int, int, int)
+ */
+ public void clearOverrideActivityTransition(@OverrideTransition int overrideType) {
+ if (overrideType != OVERRIDE_TRANSITION_OPEN && overrideType != OVERRIDE_TRANSITION_CLOSE) {
+ throw new IllegalArgumentException("Override type must be either open or close");
+ }
+ ActivityClient.getInstance().clearOverrideActivityTransition(mToken,
+ overrideType == OVERRIDE_TRANSITION_OPEN);
+ }
+
+ /**
+ * Call immediately after one of the flavors of {@link #startActivity(Intent)}
+ * or {@link #finish} to specify an explicit transition animation to
+ * perform next.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+ * to using this with starting activities is to supply the desired animation
+ * information through a {@link ActivityOptions} bundle to
+ * {@link #startActivity(Intent, Bundle)} or a related function. This allows
+ * you to specify a custom animation even when starting an activity from
+ * outside the context of the current top activity.
+ *
+ * <p>Af of {@link android.os.Build.VERSION_CODES#S} application can only specify
+ * a transition animation when the transition happens within the same task. System
+ * default animation is used for cross-task transition animations.
+ *
+ * @param enterAnim A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @deprecated Use {@link #overrideActivityTransition(int, int, int)}} instead.
+ */
+ @Deprecated
+ public void overridePendingTransition(int enterAnim, int exitAnim) {
+ overridePendingTransition(enterAnim, exitAnim, 0);
+ }
+
+ /**
+ * Call immediately after one of the flavors of {@link #startActivity(Intent)}
+ * or {@link #finish} to specify an explicit transition animation to
+ * perform next.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+ * to using this with starting activities is to supply the desired animation
+ * information through a {@link ActivityOptions} bundle to
+ * {@link #startActivity(Intent, Bundle)} or a related function. This allows
+ * you to specify a custom animation even when starting an activity from
+ * outside the context of the current top activity.
+ *
+ * @param enterAnim A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation if
+ * the animation requires a background. Set to 0 to not override the default color.
+ * @deprecated Use {@link #overrideActivityTransition(int, int, int, int)}} instead.
+ */
+ @Deprecated
+ public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) {
+ ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim,
+ exitAnim, backgroundColor);
+ }
+
+ /**
+ * Call this to set the result that your activity will return to its
+ * caller.
+ *
+ * @param resultCode The result code to propagate back to the originating
+ * activity, often RESULT_CANCELED or RESULT_OK
+ *
+ * @see #RESULT_CANCELED
+ * @see #RESULT_OK
+ * @see #RESULT_FIRST_USER
+ * @see #setResult(int, Intent)
+ */
+ public final void setResult(int resultCode) {
+ synchronized (this) {
+ mResultCode = resultCode;
+ mResultData = null;
+ }
+ }
+
+ /**
+ * Ensures the activity's result is immediately returned to the caller when {@link #finish()}
+ * is invoked
+ *
+ * <p>Should be invoked alongside {@link #setResult(int, Intent)}, so the provided results are
+ * in place before finishing. Must only be invoked during MediaProjection setup.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ public final void setForceSendResultForMediaProjection() {
+ ActivityClient.getInstance().setForceSendResultForMediaProjection(mToken);
+ }
+
+ /**
+ * Call this to set the result that your activity will return to its
+ * caller.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, the Intent
+ * you supply here can have {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} set. This will grant the
+ * Activity receiving the result access to the specific URIs in the Intent.
+ * Access will remain until the Activity has finished (it will remain across the hosting
+ * process being killed and other temporary destruction) and will be added
+ * to any existing set of URI permissions it already holds.
+ *
+ * @param resultCode The result code to propagate back to the originating
+ * activity, often RESULT_CANCELED or RESULT_OK
+ * @param data The data to propagate back to the originating activity.
+ *
+ * @see #RESULT_CANCELED
+ * @see #RESULT_OK
+ * @see #RESULT_FIRST_USER
+ * @see #setResult(int)
+ */
+ public final void setResult(int resultCode, Intent data) {
+ synchronized (this) {
+ mResultCode = resultCode;
+ mResultData = data;
+ }
+ }
+
+ /**
+ * Return information about who launched this activity. If the launching Intent
+ * contains an {@link android.content.Intent#EXTRA_REFERRER Intent.EXTRA_REFERRER},
+ * that will be returned as-is; otherwise, if known, an
+ * {@link Intent#URI_ANDROID_APP_SCHEME android-app:} referrer URI containing the
+ * package name that started the Intent will be returned. This may return null if no
+ * referrer can be identified -- it is neither explicitly specified, nor is it known which
+ * application package was involved.
+ *
+ * <p>If called while inside the handling of {@link #onNewIntent}, this function will
+ * return the referrer that submitted that new intent to the activity. Otherwise, it
+ * always returns the referrer of the original Intent.</p>
+ *
+ * <p>Note that this is <em>not</em> a security feature -- you can not trust the
+ * referrer information, applications can spoof it.</p>
+ */
+ @Nullable
+ public Uri getReferrer() {
+ Intent intent = getIntent();
+ if (intent != null) {
+ try {
+ Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER, android.net.Uri.class);
+ if (referrer != null) {
+ return referrer;
+ }
+ String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME);
+ if (referrerName != null) {
+ return Uri.parse(referrerName);
+ }
+ } catch (BadParcelableException e) {
+ Log.w(TAG, "Cannot read referrer from intent;"
+ + " intent extras contain unknown custom Parcelable objects");
+ }
+ }
+ if (mReferrer != null) {
+ return new Uri.Builder().scheme("android-app").authority(mReferrer).build();
+ }
+ return null;
+ }
+
+ /**
+ * Override to generate the desired referrer for the content currently being shown
+ * by the app. The default implementation returns null, meaning the referrer will simply
+ * be the android-app: of the package name of this activity. Return a non-null Uri to
+ * have that supplied as the {@link Intent#EXTRA_REFERRER} of any activities started from it.
+ */
+ public Uri onProvideReferrer() {
+ return null;
+ }
+
+ /**
+ * Return the name of the package that invoked this activity. This is who
+ * the data in {@link #setResult setResult()} will be sent to. You can
+ * use this information to validate that the recipient is allowed to
+ * receive the data.
+ *
+ * <p class="note">Note: if the calling activity is not expecting a result (that is it
+ * did not use the {@link #startActivityForResult}
+ * form that includes a request code), then the calling package will be
+ * null.</p>
+ *
+ * <p class="note">Note: prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * the result from this method was unstable. If the process hosting the calling
+ * package was no longer running, it would return null instead of the proper package
+ * name. You can use {@link #getCallingActivity()} and retrieve the package name
+ * from that instead.</p>
+ *
+ * @return The package of the activity that will receive your
+ * reply, or null if none.
+ */
+ @Nullable
+ public String getCallingPackage() {
+ return ActivityClient.getInstance().getCallingPackage(mToken);
+ }
+
+ /**
+ * Return the name of the activity that invoked this activity. This is
+ * who the data in {@link #setResult setResult()} will be sent to. You
+ * can use this information to validate that the recipient is allowed to
+ * receive the data.
+ *
+ * <p class="note">Note: if the calling activity is not expecting a result (that is it
+ * did not use the {@link #startActivityForResult}
+ * form that includes a request code), then the calling package will be
+ * null.
+ *
+ * @return The ComponentName of the activity that will receive your
+ * reply, or null if none.
+ */
+ @Nullable
+ public ComponentName getCallingActivity() {
+ return ActivityClient.getInstance().getCallingActivity(mToken);
+ }
+
+ /**
+ * Returns the uid of the app that initially launched this activity.
+ *
+ * <p>In order to receive the launching app's uid, at least one of the following has to
+ * be met:
+ * <ul>
+ * <li>The app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} with a
+ * value of {@code true} and launch this activity with the resulting {@code
+ * ActivityOptions}.
+ * <li>The launched activity has the same uid as the launching app.
+ * <li>The launched activity is running in a package that is signed with the same key
+ * used to sign the platform (typically only system packages such as Settings will
+ * meet this requirement).
+ * </ul>.
+ * These are the same requirements for {@link #getLaunchedFromPackage()}; if any of these are
+ * met, then these methods can be used to obtain the uid and package name of the launching
+ * app. If none are met, then {@link Process#INVALID_UID} is returned.
+ *
+ * <p>Note, even if the above conditions are not met, the launching app's identity may
+ * still be available from {@link #getCallingPackage()} if this activity was started with
+ * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+ *
+ * @return the uid of the launching app or {@link Process#INVALID_UID} if the current
+ * activity cannot access the identity of the launching app
+ *
+ * @see ActivityOptions#setShareIdentityEnabled(boolean)
+ * @see #getLaunchedFromPackage()
+ */
+ public int getLaunchedFromUid() {
+ return ActivityClient.getInstance().getLaunchedFromUid(getActivityToken());
+ }
+
+ /**
+ * Returns the package name of the app that initially launched this activity.
+ *
+ * <p>In order to receive the launching app's package name, at least one of the following has
+ * to be met:
+ * <ul>
+ * <li>The app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} with a
+ * value of {@code true} and launch this activity with the resulting
+ * {@code ActivityOptions}.
+ * <li>The launched activity has the same uid as the launching app.
+ * <li>The launched activity is running in a package that is signed with the same key
+ * used to sign the platform (typically only system packages such as Settings will
+ * meet this requirement).
+ * </ul>.
+ * These are the same requirements for {@link #getLaunchedFromUid()}; if any of these are
+ * met, then these methods can be used to obtain the uid and package name of the launching
+ * app. If none are met, then {@code null} is returned.
+ *
+ * <p>Note, even if the above conditions are not met, the launching app's identity may
+ * still be available from {@link #getCallingPackage()} if this activity was started with
+ * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+ *
+ * @return the package name of the launching app or null if the current activity
+ * cannot access the identity of the launching app
+ *
+ * @see ActivityOptions#setShareIdentityEnabled(boolean)
+ * @see #getLaunchedFromUid()
+ */
+ @Nullable
+ public String getLaunchedFromPackage() {
+ return ActivityClient.getInstance().getLaunchedFromPackage(getActivityToken());
+ }
+
+ /**
+ * Control whether this activity's main window is visible. This is intended
+ * only for the special case of an activity that is not going to show a
+ * UI itself, but can't just finish prior to onResume() because it needs
+ * to wait for a service binding or such. Setting this to false allows
+ * you to prevent your UI from being shown during that time.
+ *
+ * <p>The default value for this is taken from the
+ * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
+ */
+ public void setVisible(boolean visible) {
+ if (mVisibleFromClient != visible) {
+ mVisibleFromClient = visible;
+ if (mVisibleFromServer) {
+ if (visible) makeVisible();
+ else mDecor.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
+ void makeVisible() {
+ if (!mWindowAdded) {
+ ViewManager wm = getWindowManager();
+ wm.addView(mDecor, getWindow().getAttributes());
+ mWindowAdded = true;
+ }
+ mDecor.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Check to see whether this activity is in the process of finishing,
+ * either because you called {@link #finish} on it or someone else
+ * has requested that it finished. This is often used in
+ * {@link #onPause} to determine whether the activity is simply pausing or
+ * completely finishing.
+ *
+ * @return If the activity is finishing, returns true; else returns false.
+ *
+ * @see #finish
+ */
+ public boolean isFinishing() {
+ return mFinished;
+ }
+
+ /**
+ * Returns true if the final {@link #onDestroy()} call has been made
+ * on the Activity, so this instance is now dead.
+ */
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ /**
+ * Check to see whether this activity is in the process of being destroyed in order to be
+ * recreated with a new configuration. This is often used in
+ * {@link #onStop} to determine whether the state needs to be cleaned up or will be passed
+ * on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}.
+ *
+ * @return If the activity is being torn down in order to be recreated with a new configuration,
+ * returns true; else returns false.
+ */
+ public boolean isChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Cause this Activity to be recreated with a new instance. This results
+ * in essentially the same flow as when the Activity is created due to
+ * a configuration change -- the current instance will go through its
+ * lifecycle to {@link #onDestroy} and a new instance then created after it.
+ */
+ public void recreate() {
+ if (mParent != null) {
+ throw new IllegalStateException("Can only be called on top-level activity");
+ }
+ if (Looper.myLooper() != mMainThread.getLooper()) {
+ throw new IllegalStateException("Must be called from main thread");
+ }
+ mMainThread.scheduleRelaunchActivity(mToken);
+ }
+
+ /**
+ * Finishes the current activity and specifies whether to remove the task associated with this
+ * activity.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private void finish(int finishTask) {
+ if (mParent == null) {
+ int resultCode;
+ Intent resultData;
+ synchronized (this) {
+ resultCode = mResultCode;
+ resultData = mResultData;
+ }
+ if (false) Log.v(TAG, "Finishing self: token=" + mToken);
+ if (resultData != null) {
+ resultData.prepareToLeaveProcess(this);
+ }
+ if (ActivityClient.getInstance().finishActivity(mToken, resultCode, resultData,
+ finishTask)) {
+ mFinished = true;
+ }
+ } else {
+ mParent.finishFromChild(this);
+ }
+
+ getAutofillClientController().onActivityFinish(mIntent);
+ }
+
+ /**
+ * Call this when your activity is done and should be closed. The
+ * ActivityResult is propagated back to whoever launched you via
+ * onActivityResult().
+ */
+ public void finish() {
+ finish(DONT_FINISH_TASK_WITH_ACTIVITY);
+ }
+
+ /**
+ * Finish this activity as well as all activities immediately below it
+ * in the current task that have the same affinity. This is typically
+ * used when an application can be launched on to another task (such as
+ * from an ACTION_VIEW of a content type it understands) and the user
+ * has used the up navigation to switch out of the current task and in
+ * to its own task. In this case, if the user has navigated down into
+ * any other activities of the second application, all of those should
+ * be removed from the original task as part of the task switch.
+ *
+ * <p>Note that this finish does <em>not</em> allow you to deliver results
+ * to the previous activity, and an exception will be thrown if you are trying
+ * to do so.</p>
+ */
+ public void finishAffinity() {
+ if (mParent != null) {
+ throw new IllegalStateException("Can not be called from an embedded activity");
+ }
+ if (mResultCode != RESULT_CANCELED || mResultData != null) {
+ throw new IllegalStateException("Can not be called to deliver a result");
+ }
+ if (ActivityClient.getInstance().finishActivityAffinity(mToken)) {
+ mFinished = true;
+ }
+ }
+
+ /**
+ * This is called when a child activity of this one calls its
+ * {@link #finish} method. The default implementation simply calls
+ * finish() on this activity (the parent), finishing the entire group.
+ *
+ * @param child The activity making the call.
+ *
+ * @see #finish
+ * @deprecated Use {@link #finish()} instead.
+ */
+ @Deprecated
+ public void finishFromChild(Activity child) {
+ finish();
+ }
+
+ /**
+ * Reverses the Activity Scene entry Transition and triggers the calling Activity
+ * to reverse its exit Transition. When the exit Transition completes,
+ * {@link #finish()} is called. If no entry Transition was used, finish() is called
+ * immediately and the Activity exit Transition is run.
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[])
+ */
+ public void finishAfterTransition() {
+ if (!mActivityTransitionState.startExitBackTransition(this)) {
+ finish();
+ }
+ }
+
+ /**
+ * Force finish another activity that you had previously started with
+ * {@link #startActivityForResult}.
+ *
+ * @param requestCode The request code of the activity that you had
+ * given to startActivityForResult(). If there are multiple
+ * activities started with this request code, they
+ * will all be finished.
+ */
+ public void finishActivity(int requestCode) {
+ if (mParent == null) {
+ ActivityClient.getInstance().finishSubActivity(mToken, mEmbeddedID, requestCode);
+ } else {
+ mParent.finishActivityFromChild(this, requestCode);
+ }
+ }
+
+ /**
+ * This is called when a child activity of this one calls its
+ * finishActivity().
+ *
+ * @param child The activity making the call.
+ * @param requestCode Request code that had been used to start the
+ * activity.
+ * @deprecated Use {@link #finishActivity(int)} instead.
+ */
+ @Deprecated
+ public void finishActivityFromChild(@NonNull Activity child, int requestCode) {
+ ActivityClient.getInstance().finishSubActivity(mToken, child.mEmbeddedID, requestCode);
+ }
+
+ /**
+ * Call this when your activity is done and should be closed and the task should be completely
+ * removed as a part of finishing the root activity of the task.
+ */
+ public void finishAndRemoveTask() {
+ finish(FINISH_TASK_WITH_ROOT_ACTIVITY);
+ }
+
+ /**
+ * Ask that the local app instance of this activity be released to free up its memory.
+ * This is asking for the activity to be destroyed, but does <b>not</b> finish the activity --
+ * a new instance of the activity will later be re-created if needed due to the user
+ * navigating back to it.
+ *
+ * @return Returns true if the activity was in a state that it has started the process
+ * of destroying its current instance; returns false if for any reason this could not
+ * be done: it is currently visible to the user, it is already being destroyed, it is
+ * being finished, it hasn't yet saved its state, etc.
+ */
+ public boolean releaseInstance() {
+ return ActivityClient.getInstance().releaseActivityInstance(mToken);
+ }
+
+ /**
+ * Called when an activity you launched exits, giving you the requestCode
+ * you started it with, the resultCode it returned, and any additional
+ * data from it. The <var>resultCode</var> will be
+ * {@link #RESULT_CANCELED} if the activity explicitly returned that,
+ * didn't return any result, or crashed during its operation.
+ *
+ * <p>An activity can never receive a result in the resumed state. You can count on
+ * {@link #onResume} being called after this method, though not necessarily immediately after.
+ * If the activity was resumed, it will be paused and the result will be delivered, followed
+ * by {@link #onResume}. If the activity wasn't in the resumed state, then the result will
+ * be delivered, with {@link #onResume} called sometime later when the activity becomes active
+ * again.
+ *
+ * <p>This method is never invoked if your activity sets
+ * {@link android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
+ * <code>true</code>.
+ *
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ *
+ * @see #startActivityForResult
+ * @see #createPendingResult
+ * @see #setResult(int)
+ */
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ }
+
+ /**
+ * Called when an activity you launched with an activity transition exposes this
+ * Activity through a returning activity transition, giving you the resultCode
+ * and any additional data from it. This method will only be called if the activity
+ * set a result code other than {@link #RESULT_CANCELED} and it supports activity
+ * transitions with {@link Window#FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * <p>The purpose of this function is to let the called Activity send a hint about
+ * its state so that this underlying Activity can prepare to be exposed. A call to
+ * this method does not guarantee that the called Activity has or will be exiting soon.
+ * It only indicates that it will expose this Activity's Window and it has
+ * some data to pass to prepare it.</p>
+ *
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ */
+ public void onActivityReenter(int resultCode, Intent data) {
+ }
+
+ /**
+ * Create a new PendingIntent object which you can hand to others
+ * for them to use to send result data back to your
+ * {@link #onActivityResult} callback. The created object will be either
+ * one-shot (becoming invalid after a result is sent back) or multiple
+ * (allowing any number of results to be sent through it).
+ *
+ * @param requestCode Private request code for the sender that will be
+ * associated with the result data when it is returned. The sender can not
+ * modify this value, allowing you to identify incoming results.
+ * @param data Default data to supply in the result, which may be modified
+ * by the sender.
+ * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT},
+ * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE},
+ * {@link PendingIntent#FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT},
+ * {@link PendingIntent#FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT},
+ * or any of the flags as supported by
+ * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+ * of the intent that can be supplied when the actual send happens.
+ *
+ * @return Returns an existing or new PendingIntent matching the given
+ * parameters. May return null only if
+ * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been
+ * supplied.
+ *
+ * @see PendingIntent
+ */
+ public PendingIntent createPendingResult(int requestCode, @NonNull Intent data,
+ @PendingIntent.Flags int flags) {
+ String packageName = getPackageName();
+ try {
+ data.prepareToLeaveProcess(this);
+ IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
+ ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, getAttributionTag(),
+ mParent == null ? mToken : mParent.mToken, mEmbeddedID, requestCode,
+ new Intent[]{data}, null, flags, null, getUserId());
+ return target != null ? new PendingIntent(target) : null;
+ } catch (RemoteException e) {
+ // Empty
+ }
+ return null;
+ }
+
+ /**
+ * Change the desired orientation of this activity. If the activity
+ * is currently in the foreground or otherwise impacting the screen
+ * orientation, the screen will immediately be changed (possibly causing
+ * the activity to be restarted). Otherwise, this will be used the next
+ * time the activity is visible.
+ *
+ * @param requestedOrientation An orientation constant as used in
+ * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+ */
+ public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
+ if (mParent == null) {
+ ActivityClient.getInstance().setRequestedOrientation(mToken, requestedOrientation);
+ } else {
+ mParent.setRequestedOrientation(requestedOrientation);
+ }
+ }
+
+ /**
+ * Return the current requested orientation of the activity. This will
+ * either be the orientation requested in its component's manifest, or
+ * the last requested orientation given to
+ * {@link #setRequestedOrientation(int)}.
+ *
+ * @return Returns an orientation constant as used in
+ * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+ */
+ @ActivityInfo.ScreenOrientation
+ public int getRequestedOrientation() {
+ if (mParent == null) {
+ return ActivityClient.getInstance().getRequestedOrientation(mToken);
+ } else {
+ return mParent.getRequestedOrientation();
+ }
+ }
+
+ /**
+ * Return the identifier of the task this activity is in. This identifier
+ * will remain the same for the lifetime of the activity.
+ *
+ * @return Task identifier, an opaque integer.
+ */
+ public int getTaskId() {
+ return ActivityClient.getInstance().getTaskForActivity(mToken, false /* onlyRoot */);
+ }
+
+ /**
+ * Return whether this activity is the root of a task. The root is the
+ * first activity in a task.
+ *
+ * @return True if this is the root activity, else false.
+ */
+ public boolean isTaskRoot() {
+ return mWindowControllerCallback.isTaskRoot();
+ }
+
+ /**
+ * Move the task containing this activity to the back of the activity
+ * stack. The activity's order within the task is unchanged.
+ *
+ * @param nonRoot If false then this only works if the activity is the root
+ * of a task; if true it will work for any activity in
+ * a task.
+ *
+ * @return If the task was moved (or it was already at the
+ * back) true is returned, else false.
+ */
+ public boolean moveTaskToBack(boolean nonRoot) {
+ return ActivityClient.getInstance().moveActivityTaskToBack(mToken, nonRoot);
+ }
+
+ /**
+ * Returns class name for this activity with the package prefix removed.
+ * This is the default name used to read and write settings.
+ *
+ * @return The local class name.
+ */
+ @NonNull
+ public String getLocalClassName() {
+ final String pkg = getPackageName();
+ final String cls = mComponent.getClassName();
+ int packageLen = pkg.length();
+ if (!cls.startsWith(pkg) || cls.length() <= packageLen
+ || cls.charAt(packageLen) != '.') {
+ return cls;
+ }
+ return cls.substring(packageLen+1);
+ }
+
+ /**
+ * Returns the complete component name of this activity.
+ *
+ * @return Returns the complete component name for this activity
+ */
+ public ComponentName getComponentName() {
+ return mComponent;
+ }
+
+ /** @hide */
+ @Override
+ public final ComponentName contentCaptureClientGetComponentName() {
+ return getComponentName();
+ }
+
+ /**
+ * Retrieve a {@link SharedPreferences} object for accessing preferences
+ * that are private to this activity. This simply calls the underlying
+ * {@link #getSharedPreferences(String, int)} method by passing in this activity's
+ * class name as the preferences name.
+ *
+ * @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default
+ * operation.
+ *
+ * @return Returns the single SharedPreferences instance that can be used
+ * to retrieve and modify the preference values.
+ */
+ public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
+ return getSharedPreferences(getLocalClassName(), mode);
+ }
+
+ /**
+ * Indicates whether this activity is launched from a bubble. A bubble is a floating shortcut
+ * on the screen that expands to show an activity.
+ *
+ * If your activity can be used normally or as a bubble, you might use this method to check
+ * if the activity is bubbled to modify any behaviour that might be different between the
+ * normal activity and the bubbled activity. For example, if you normally cancel the
+ * notification associated with the activity when you open the activity, you might not want to
+ * do that when you're bubbled as that would remove the bubble.
+ *
+ * @return {@code true} if the activity is launched from a bubble.
+ *
+ * @see Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata)
+ * @see Notification.BubbleMetadata.Builder#Builder(String)
+ */
+ public boolean isLaunchedFromBubble() {
+ return mLaunchedFromBubble;
+ }
+
+ private void ensureSearchManager() {
+ if (mSearchManager != null) {
+ return;
+ }
+
+ try {
+ mSearchManager = new SearchManager(this, null);
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public Object getSystemService(@ServiceName @NonNull String name) {
+ if (getBaseContext() == null) {
+ throw new IllegalStateException(
+ "System services not available to Activities before onCreate()");
+ }
+
+ if (WINDOW_SERVICE.equals(name)) {
+ return mWindowManager;
+ } else if (SEARCH_SERVICE.equals(name)) {
+ ensureSearchManager();
+ return mSearchManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ /**
+ * Change the title associated with this activity. If this is a
+ * top-level activity, the title for its window will change. If it
+ * is an embedded activity, the parent can do whatever it wants
+ * with it.
+ */
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ onTitleChanged(title, mTitleColor);
+
+ if (mParent != null) {
+ mParent.onChildTitleChanged(this, title);
+ }
+ }
+
+ /**
+ * Change the title associated with this activity. If this is a
+ * top-level activity, the title for its window will change. If it
+ * is an embedded activity, the parent can do whatever it wants
+ * with it.
+ */
+ public void setTitle(int titleId) {
+ setTitle(getText(titleId));
+ }
+
+ /**
+ * Change the color of the title associated with this activity.
+ * <p>
+ * This method is deprecated starting in API Level 11 and replaced by action
+ * bar styles. For information on styling the Action Bar, read the <a
+ * href="{@docRoot} guide/topics/ui/actionbar.html">Action Bar</a> developer
+ * guide.
+ *
+ * @deprecated Use action bar styles instead.
+ */
+ @Deprecated
+ public void setTitleColor(int textColor) {
+ mTitleColor = textColor;
+ onTitleChanged(mTitle, textColor);
+ }
+
+ public final CharSequence getTitle() {
+ return mTitle;
+ }
+
+ public final int getTitleColor() {
+ return mTitleColor;
+ }
+
+ protected void onTitleChanged(CharSequence title, int color) {
+ if (mTitleReady) {
+ final Window win = getWindow();
+ if (win != null) {
+ win.setTitle(title);
+ if (color != 0) {
+ win.setTitleColor(color);
+ }
+ }
+ if (mActionBar != null) {
+ mActionBar.setWindowTitle(title);
+ }
+ }
+ }
+
+ protected void onChildTitleChanged(Activity childActivity, CharSequence title) {
+ }
+
+ /**
+ * Sets information describing the task with this activity for presentation inside the Recents
+ * System UI. When {@link ActivityManager#getRecentTasks} is called, the activities of each task
+ * are traversed in order from the topmost activity to the bottommost. The traversal continues
+ * for each property until a suitable value is found. For each task the taskDescription will be
+ * returned in {@link android.app.ActivityManager.TaskDescription}.
+ *
+ * @see ActivityManager#getRecentTasks
+ * @see android.app.ActivityManager.TaskDescription
+ *
+ * @param taskDescription The TaskDescription properties that describe the task with this activity
+ */
+ public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
+ if (mTaskDescription != taskDescription) {
+ mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
+ // Scale the icon down to something reasonable if it is provided
+ if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
+ final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
+ final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
+ true);
+ mTaskDescription.setIcon(Icon.createWithBitmap(icon));
+ }
+ }
+ ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription);
+ }
+
+ /**
+ * Sets the visibility of the progress bar in the title.
+ * <p>
+ * In order for the progress bar to be shown, the feature must be requested
+ * via {@link #requestWindowFeature(int)}.
+ *
+ * @param visible Whether to show the progress bars in the title.
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public final void setProgressBarVisibility(boolean visible) {
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON :
+ Window.PROGRESS_VISIBILITY_OFF);
+ }
+
+ /**
+ * Sets the visibility of the indeterminate progress bar in the title.
+ * <p>
+ * In order for the progress bar to be shown, the feature must be requested
+ * via {@link #requestWindowFeature(int)}.
+ *
+ * @param visible Whether to show the progress bars in the title.
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public final void setProgressBarIndeterminateVisibility(boolean visible) {
+ getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+ visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF);
+ }
+
+ /**
+ * Sets whether the horizontal progress bar in the title should be indeterminate (the circular
+ * is always indeterminate).
+ * <p>
+ * In order for the progress bar to be shown, the feature must be requested
+ * via {@link #requestWindowFeature(int)}.
+ *
+ * @param indeterminate Whether the horizontal progress bar should be indeterminate.
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public final void setProgressBarIndeterminate(boolean indeterminate) {
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ indeterminate ? Window.PROGRESS_INDETERMINATE_ON
+ : Window.PROGRESS_INDETERMINATE_OFF);
+ }
+
+ /**
+ * Sets the progress for the progress bars in the title.
+ * <p>
+ * In order for the progress bar to be shown, the feature must be requested
+ * via {@link #requestWindowFeature(int)}.
+ *
+ * @param progress The progress for the progress bar. Valid ranges are from
+ * 0 to 10000 (both inclusive). If 10000 is given, the progress
+ * bar will be completely filled and will fade out.
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public final void setProgress(int progress) {
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START);
+ }
+
+ /**
+ * Sets the secondary progress for the progress bar in the title. This
+ * progress is drawn between the primary progress (set via
+ * {@link #setProgress(int)} and the background. It can be ideal for media
+ * scenarios such as showing the buffering progress while the default
+ * progress shows the play progress.
+ * <p>
+ * In order for the progress bar to be shown, the feature must be requested
+ * via {@link #requestWindowFeature(int)}.
+ *
+ * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from
+ * 0 to 10000 (both inclusive).
+ * @deprecated No longer supported starting in API 21.
+ */
+ @Deprecated
+ public final void setSecondaryProgress(int secondaryProgress) {
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ secondaryProgress + Window.PROGRESS_SECONDARY_START);
+ }
+
+ /**
+ * Suggests an audio stream whose volume should be changed by the hardware
+ * volume controls.
+ * <p>
+ * The suggested audio stream will be tied to the window of this Activity.
+ * Volume requests which are received while the Activity is in the
+ * foreground will affect this stream.
+ * <p>
+ * It is not guaranteed that the hardware volume controls will always change
+ * this stream's volume (for example, if a call is in progress, its stream's
+ * volume may be changed instead). To reset back to the default, use
+ * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+ *
+ * @param streamType The type of the audio stream whose volume should be
+ * changed by the hardware volume controls.
+ */
+ public final void setVolumeControlStream(int streamType) {
+ getWindow().setVolumeControlStream(streamType);
+ }
+
+ /**
+ * Gets the suggested audio stream whose volume should be changed by the
+ * hardware volume controls.
+ *
+ * @return The suggested audio stream type whose volume should be changed by
+ * the hardware volume controls.
+ * @see #setVolumeControlStream(int)
+ */
+ public final int getVolumeControlStream() {
+ return getWindow().getVolumeControlStream();
+ }
+
+ /**
+ * Sets a {@link MediaController} to send media keys and volume changes to.
+ * <p>
+ * The controller will be tied to the window of this Activity. Media key and
+ * volume events which are received while the Activity is in the foreground
+ * will be forwarded to the controller and used to invoke transport controls
+ * or adjust the volume. This may be used instead of or in addition to
+ * {@link #setVolumeControlStream} to affect a specific session instead of a
+ * specific stream.
+ * <p>
+ * It is not guaranteed that the hardware volume controls will always change
+ * this session's volume (for example, if a call is in progress, its
+ * stream's volume may be changed instead). To reset back to the default use
+ * null as the controller.
+ *
+ * @param controller The controller for the session which should receive
+ * media keys and volume changes.
+ */
+ public final void setMediaController(MediaController controller) {
+ getWindow().setMediaController(controller);
+ }
+
+ /**
+ * Gets the controller which should be receiving media key and volume events
+ * while this activity is in the foreground.
+ *
+ * @return The controller which should receive events.
+ * @see #setMediaController(android.media.session.MediaController)
+ */
+ public final MediaController getMediaController() {
+ return getWindow().getMediaController();
+ }
+
+ /**
+ * Runs the specified action on the UI thread. If the current thread is the UI
+ * thread, then the action is executed immediately. If the current thread is
+ * not the UI thread, the action is posted to the event queue of the UI thread.
+ *
+ * @param action the action to run on the UI thread
+ */
+ public final void runOnUiThread(Runnable action) {
+ if (Thread.currentThread() != mUiThread) {
+ mHandler.post(action);
+ } else {
+ action.run();
+ }
+ }
+
+ /**
+ * Standard implementation of
+ * {@link android.view.LayoutInflater.Factory#onCreateView} used when
+ * inflating with the LayoutInflater returned by {@link #getSystemService}.
+ * This implementation does nothing and is for
+ * pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} apps. Newer apps
+ * should use {@link #onCreateView(View, String, Context, AttributeSet)}.
+ *
+ * @see android.view.LayoutInflater#createView
+ * @see android.view.Window#getLayoutInflater
+ */
+ @Nullable
+ public View onCreateView(@NonNull String name, @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ return null;
+ }
+
+ /**
+ * Standard implementation of
+ * {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)}
+ * used when inflating with the LayoutInflater returned by {@link #getSystemService}.
+ * This implementation handles <fragment> tags to embed fragments inside
+ * of the activity.
+ *
+ * @see android.view.LayoutInflater#createView
+ * @see android.view.Window#getLayoutInflater
+ */
+ @Nullable
+ public View onCreateView(@Nullable View parent, @NonNull String name,
+ @NonNull Context context, @NonNull AttributeSet attrs) {
+ if (!"fragment".equals(name)) {
+ return onCreateView(name, context, attrs);
+ }
+
+ return mFragments.onCreateView(parent, name, context, attrs);
+ }
+
+ /**
+ * Print the Activity's state into the given stream. This gets invoked if
+ * you run <code>adb shell dumpsys activity <activity_component_name></code>.
+ *
+ * <p>This method won't be called if the app targets
+ * {@link android.os.Build.VERSION_CODES#TIRAMISU} or later if the dump request starts with one
+ * of the following arguments:
+ * <ul>
+ * <li>--autofill
+ * <li>--contentcapture
+ * <li>--translation
+ * <li>--list-dumpables
+ * <li>--dump-dumpable
+ * </ul>
+ *
+ * @param prefix Desired prefix to prepend at each line of output.
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ dumpInner(prefix, fd, writer, args);
+ }
+
+ /**
+ * See {@link android.util.DumpableContainer#addDumpable(Dumpable)}.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final boolean addDumpable(@NonNull Dumpable dumpable) {
+ if (mDumpableContainer == null) {
+ mDumpableContainer = new DumpableContainerImpl();
+ }
+ return mDumpableContainer.addDumpable(dumpable);
+ }
+
+ /**
+ * This is the real method called by {@code ActivityThread}, but it's also exposed so
+ * CTS can test for the special args cases.
+ *
+ * @hide
+ */
+ @TestApi
+ @VisibleForTesting
+ @SuppressLint("OnNameExpected")
+ public void dumpInternal(@NonNull String prefix,
+ @SuppressLint("UseParcelFileDescriptor") @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+
+ // Lazy-load mDumpableContainer with Dumpables activity might already have a reference to
+ if (mAutofillClientController != null) {
+ addDumpable(mAutofillClientController);
+ }
+ if (mUiTranslationController != null) {
+ addDumpable(mUiTranslationController);
+ }
+ if (mContentCaptureManager != null) {
+ mContentCaptureManager.addDumpable(this);
+ }
+
+ boolean dumpInternalState = true;
+ String arg = null;
+ if (args != null && args.length > 0) {
+ arg = args[0];
+ boolean isSpecialCase = true;
+ // Handle special cases
+ switch (arg) {
+ case DUMP_ARG_AUTOFILL:
+ dumpLegacyDumpable(prefix, writer, arg,
+ AutofillClientController.DUMPABLE_NAME);
+ return;
+ case DUMP_ARG_CONTENT_CAPTURE:
+ dumpLegacyDumpable(prefix, writer, arg,
+ ContentCaptureManager.DUMPABLE_NAME);
+ return;
+ case DUMP_ARG_TRANSLATION:
+ dumpLegacyDumpable(prefix, writer, arg,
+ UiTranslationController.DUMPABLE_NAME);
+ return;
+ case DUMP_ARG_LIST_DUMPABLES:
+ if (mDumpableContainer == null) {
+ writer.print(prefix); writer.println("No dumpables");
+ } else {
+ mDumpableContainer.listDumpables(prefix, writer);
+ }
+ return;
+ case DUMP_ARG_DUMP_DUMPABLE:
+ if (args.length == 1) {
+ writer.print(DUMP_ARG_DUMP_DUMPABLE);
+ writer.println(" requires the dumpable name");
+ } else if (mDumpableContainer == null) {
+ writer.println("no dumpables");
+ } else {
+ // Strips --dump-dumpable NAME
+ String[] prunedArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, prunedArgs, 0, prunedArgs.length);
+ mDumpableContainer.dumpOneDumpable(prefix, writer, args[1], prunedArgs);
+ }
+ break;
+ default:
+ isSpecialCase = false;
+ break;
+ }
+ if (isSpecialCase) {
+ dumpInternalState = !CompatChanges.isChangeEnabled(DUMP_IGNORES_SPECIAL_ARGS);
+ }
+ }
+
+ if (dumpInternalState) {
+ dump(prefix, fd, writer, args);
+ } else {
+ Log.i(TAG, "Not calling dump() on " + this + " because of special argument " + arg);
+ }
+ }
+
+ void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ String innerPrefix = prefix + " ";
+
+ writer.print(prefix); writer.print("Local Activity ");
+ writer.print(Integer.toHexString(System.identityHashCode(this)));
+ writer.println(" State:");
+ writer.print(innerPrefix); writer.print("mResumed=");
+ writer.print(mResumed); writer.print(" mStopped=");
+ writer.print(mStopped); writer.print(" mFinished=");
+ writer.println(mFinished);
+ writer.print(innerPrefix); writer.print("mIsInMultiWindowMode=");
+ writer.print(mIsInMultiWindowMode);
+ writer.print(" mIsInPictureInPictureMode=");
+ writer.println(mIsInPictureInPictureMode);
+ writer.print(innerPrefix); writer.print("mChangingConfigurations=");
+ writer.println(mChangingConfigurations);
+ writer.print(innerPrefix); writer.print("mCurrentConfig=");
+ writer.println(mCurrentConfig);
+
+ mFragments.dumpLoaders(innerPrefix, fd, writer, args);
+ mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.dump(innerPrefix, fd, writer, args);
+ }
+
+ if (getWindow() != null &&
+ getWindow().peekDecorView() != null &&
+ getWindow().peekDecorView().getViewRootImpl() != null) {
+ getWindow().peekDecorView().getViewRootImpl().dump(prefix, writer);
+ }
+
+ mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
+
+ ResourcesManager.getInstance().dump(prefix, writer);
+
+ if (mDumpableContainer != null) {
+ mDumpableContainer.dumpAllDumpables(prefix, writer, args);
+ }
+ }
+
+ private void dumpLegacyDumpable(String prefix, PrintWriter writer, String legacyOption,
+ String dumpableName) {
+ writer.printf("%s%s option deprecated. Use %s %s instead\n", prefix, legacyOption,
+ DUMP_ARG_DUMP_DUMPABLE, dumpableName);
+ }
+
+ /**
+ * Bit indicating that this activity is "immersive" and should not be
+ * interrupted by notifications if possible.
+ *
+ * This value is initially set by the manifest property
+ * <code>android:immersive</code> but may be changed at runtime by
+ * {@link #setImmersive}.
+ *
+ * @see #setImmersive(boolean)
+ * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+ */
+ public boolean isImmersive() {
+ return ActivityClient.getInstance().isImmersive(mToken);
+ }
+
+ /**
+ * Indication of whether this is the highest level activity in this task. Can be used to
+ * determine whether an activity launched by this activity was placed in the same task or
+ * another task.
+ *
+ * @return true if this is the topmost, non-finishing activity in its task.
+ */
+ final boolean isTopOfTask() {
+ if (mToken == null || mWindow == null) {
+ return false;
+ }
+ return ActivityClient.getInstance().isTopOfTask(getActivityToken());
+ }
+
+ /**
+ * Convert an activity, which particularly with {@link android.R.attr#windowIsTranslucent} or
+ * {@link android.R.attr#windowIsFloating} attribute, to a fullscreen opaque activity, or
+ * convert it from opaque back to translucent.
+ *
+ * @param translucent {@code true} convert from opaque to translucent.
+ * {@code false} convert from translucent to opaque.
+ * @return The result of setting translucency. Return {@code true} if set successfully,
+ * {@code false} otherwise.
+ */
+ public boolean setTranslucent(boolean translucent) {
+ if (translucent) {
+ return convertToTranslucent(null /* callback */, null /* options */);
+ } else {
+ return convertFromTranslucentInternal();
+ }
+ }
+
+ /**
+ * Convert an activity to a fullscreen opaque activity.
+ * <p>
+ * Call this whenever the background of a translucent activity has changed to become opaque.
+ * Doing so will allow the {@link android.view.Surface} of the activity behind to be released.
+ *
+ * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,
+ * ActivityOptions)
+ * @see TranslucentConversionListener
+ *
+ * @hide
+ */
+ @SystemApi
+ public void convertFromTranslucent() {
+ convertFromTranslucentInternal();
+ }
+
+ private boolean convertFromTranslucentInternal() {
+ mTranslucentCallback = null;
+ if (ActivityClient.getInstance().convertFromTranslucent(mToken)) {
+ WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true /* opaque */);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Convert an activity to a translucent activity.
+ * <p>
+ * Calling this allows the activity behind this one to be seen again. Once all such activities
+ * have been redrawn {@link TranslucentConversionListener#onTranslucentConversionComplete} will
+ * be called indicating that it is safe to make this activity translucent again. Until
+ * {@link TranslucentConversionListener#onTranslucentConversionComplete} is called the image
+ * behind the frontmost activity will be indeterminate.
+ *
+ * @param callback the method to call when all visible activities behind this one have been
+ * drawn and it is safe to make this activity translucent again.
+ * @param options activity options delivered to the activity below this one. The options
+ * are retrieved using {@link #getActivityOptions}.
+ * @return <code>true</code> if Window was opaque and will become translucent or
+ * <code>false</code> if window was translucent and no change needed to be made.
+ *
+ * @see #convertFromTranslucent()
+ * @see TranslucentConversionListener
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean convertToTranslucent(TranslucentConversionListener callback,
+ ActivityOptions options) {
+ mTranslucentCallback = callback;
+ mChangeCanvasToTranslucent = ActivityClient.getInstance().convertToTranslucent(
+ mToken, options == null ? null : options.toBundle());
+ WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
+
+ if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) {
+ // Window is already translucent.
+ mTranslucentCallback.onTranslucentConversionComplete(true /* drawComplete */);
+ }
+ return mChangeCanvasToTranslucent;
+ }
+
+ /** @hide */
+ void onTranslucentConversionComplete(boolean drawComplete) {
+ if (mTranslucentCallback != null) {
+ mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
+ mTranslucentCallback = null;
+ }
+ if (mChangeCanvasToTranslucent) {
+ WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
+ }
+ }
+
+ /** @hide */
+ public void onNewActivityOptions(ActivityOptions options) {
+ mActivityTransitionState.setEnterActivityOptions(this, options);
+ if (!mStopped) {
+ mActivityTransitionState.enterReady(this);
+ }
+ }
+
+ /**
+ * Takes the ActivityOptions passed in from the launching activity or passed back
+ * from an activity launched by this activity in its call to {@link
+ * #convertToTranslucent(TranslucentConversionListener, ActivityOptions)}
+ *
+ * @return The ActivityOptions passed to {@link #convertToTranslucent}.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ ActivityOptions getActivityOptions() {
+ final ActivityOptions options = mPendingOptions;
+ // The option only applies once.
+ mPendingOptions = null;
+ return options;
+ }
+
+ /**
+ * Activities that want to remain visible behind a translucent activity above them must call
+ * this method anytime between the start of {@link #onResume()} and the return from
+ * {@link #onPause()}. If this call is successful then the activity will remain visible after
+ * {@link #onPause()} is called, and is allowed to continue playing media in the background.
+ *
+ * <p>The actions of this call are reset each time that this activity is brought to the
+ * front. That is, every time {@link #onResume()} is called the activity will be assumed
+ * to not have requested visible behind. Therefore, if you want this activity to continue to
+ * be visible in the background you must call this method again.
+ *
+ * <p>Only fullscreen opaque activities may make this call. I.e. this call is a nop
+ * for dialog and translucent activities.
+ *
+ * <p>Under all circumstances, the activity must stop playing and release resources prior to or
+ * within a call to {@link #onVisibleBehindCanceled()} or if this call returns false.
+ *
+ * <p>False will be returned any time this method is called between the return of onPause and
+ * the next call to onResume.
+ *
+ * @deprecated This method's functionality is no longer supported as of
+ * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+ *
+ * @param visible true to notify the system that the activity wishes to be visible behind other
+ * translucent activities, false to indicate otherwise. Resources must be
+ * released when passing false to this method.
+ *
+ * @return the resulting visibiity state. If true the activity will remain visible beyond
+ * {@link #onPause()} if the next activity is translucent or not fullscreen. If false
+ * then the activity may not count on being visible behind other translucent activities,
+ * and must stop any media playback and release resources.
+ * Returning false may occur in lieu of a call to {@link #onVisibleBehindCanceled()} so
+ * the return value must be checked.
+ *
+ * @see #onVisibleBehindCanceled()
+ */
+ @Deprecated
+ public boolean requestVisibleBehind(boolean visible) {
+ return false;
+ }
+
+ /**
+ * Called when a translucent activity over this activity is becoming opaque or another
+ * activity is being launched. Activities that override this method must call
+ * <code>super.onVisibleBehindCanceled()</code> or a SuperNotCalledException will be thrown.
+ *
+ * <p>When this method is called the activity has 500 msec to release any resources it may be
+ * using while visible in the background.
+ * If the activity has not returned from this method in 500 msec the system will destroy
+ * the activity and kill the process in order to recover the resources for another
+ * process. Otherwise {@link #onStop()} will be called following return.
+ *
+ * @see #requestVisibleBehind(boolean)
+ *
+ * @deprecated This method's functionality is no longer supported as of
+ * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+ */
+ @Deprecated
+ @CallSuper
+ public void onVisibleBehindCanceled() {
+ mCalled = true;
+ }
+
+ /**
+ * Translucent activities may call this to determine if there is an activity below them that
+ * is currently set to be visible in the background.
+ *
+ * @deprecated This method's functionality is no longer supported as of
+ * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+ *
+ * @return true if an activity below is set to visible according to the most recent call to
+ * {@link #requestVisibleBehind(boolean)}, false otherwise.
+ *
+ * @see #requestVisibleBehind(boolean)
+ * @see #onVisibleBehindCanceled()
+ * @see #onBackgroundVisibleBehindChanged(boolean)
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public boolean isBackgroundVisibleBehind() {
+ return false;
+ }
+
+ /**
+ * The topmost foreground activity will receive this call when the background visibility state
+ * of the activity below it changes.
+ *
+ * This call may be a consequence of {@link #requestVisibleBehind(boolean)} or might be
+ * due to a background activity finishing itself.
+ *
+ * @deprecated This method's functionality is no longer supported as of
+ * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+ *
+ * @param visible true if a background activity is visible, false otherwise.
+ *
+ * @see #requestVisibleBehind(boolean)
+ * @see #onVisibleBehindCanceled()
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public void onBackgroundVisibleBehindChanged(boolean visible) {
+ }
+
+ /**
+ * Activities cannot draw during the period that their windows are animating in. In order
+ * to know when it is safe to begin drawing they can override this method which will be
+ * called when the entering animation has completed.
+ */
+ public void onEnterAnimationComplete() {
+ }
+
+ /**
+ * @hide
+ */
+ public void dispatchEnterAnimationComplete() {
+ mEnterAnimationComplete = true;
+ mInstrumentation.onEnterAnimationComplete();
+ onEnterAnimationComplete();
+ if (getWindow() != null && getWindow().getDecorView() != null) {
+ View decorView = getWindow().getDecorView();
+ decorView.getViewTreeObserver().dispatchOnEnterAnimationComplete();
+ }
+ }
+
+ /**
+ * Adjust the current immersive mode setting.
+ *
+ * Note that changing this value will have no effect on the activity's
+ * {@link android.content.pm.ActivityInfo} structure; that is, if
+ * <code>android:immersive</code> is set to <code>true</code>
+ * in the application's manifest entry for this activity, the {@link
+ * android.content.pm.ActivityInfo#flags ActivityInfo.flags} member will
+ * always have its {@link android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+ * FLAG_IMMERSIVE} bit set.
+ *
+ * @see #isImmersive()
+ * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+ */
+ public void setImmersive(boolean i) {
+ ActivityClient.getInstance().setImmersive(mToken, i);
+ }
+
+ /**
+ * Enable or disable virtual reality (VR) mode for this Activity.
+ *
+ * <p>VR mode is a hint to Android system to switch to a mode optimized for VR applications
+ * while this Activity has user focus.</p>
+ *
+ * <p>It is recommended that applications additionally declare
+ * {@link android.R.attr#enableVrMode} in their manifest to allow for smooth activity
+ * transitions when switching between VR activities.</p>
+ *
+ * <p>If the requested {@link android.service.vr.VrListenerService} component is not available,
+ * VR mode will not be started. Developers can handle this case as follows:</p>
+ *
+ * <pre>
+ * String servicePackage = "com.whatever.app";
+ * String serviceClass = "com.whatever.app.MyVrListenerService";
+ *
+ * // Name of the component of the VrListenerService to start.
+ * ComponentName serviceComponent = new ComponentName(servicePackage, serviceClass);
+ *
+ * try {
+ * setVrModeEnabled(true, myComponentName);
+ * } catch (PackageManager.NameNotFoundException e) {
+ * List<ApplicationInfo> installed = getPackageManager().getInstalledApplications(0);
+ * boolean isInstalled = false;
+ * for (ApplicationInfo app : installed) {
+ * if (app.packageName.equals(servicePackage)) {
+ * isInstalled = true;
+ * break;
+ * }
+ * }
+ * if (isInstalled) {
+ * // Package is installed, but not enabled in Settings. Let user enable it.
+ * startActivity(new Intent(Settings.ACTION_VR_LISTENER_SETTINGS));
+ * } else {
+ * // Package is not installed. Send an intent to download this.
+ * sentIntentToLaunchAppStore(servicePackage);
+ * }
+ * }
+ * </pre>
+ *
+ * @param enabled {@code true} to enable this mode.
+ * @param requestedComponent the name of the component to use as a
+ * {@link android.service.vr.VrListenerService} while VR mode is enabled.
+ *
+ * @throws android.content.pm.PackageManager.NameNotFoundException if the given component
+ * to run as a {@link android.service.vr.VrListenerService} is not installed, or has
+ * not been enabled in user settings.
+ *
+ * @see android.content.pm.PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE
+ * @see android.service.vr.VrListenerService
+ * @see android.provider.Settings#ACTION_VR_LISTENER_SETTINGS
+ * @see android.R.attr#enableVrMode
+ */
+ public void setVrModeEnabled(boolean enabled, @NonNull ComponentName requestedComponent)
+ throws PackageManager.NameNotFoundException {
+ if (ActivityClient.getInstance().setVrMode(mToken, enabled, requestedComponent) != 0) {
+ throw new PackageManager.NameNotFoundException(requestedComponent.flattenToString());
+ }
+ }
+
+ /**
+ * Start an action mode of the default type {@link ActionMode#TYPE_PRIMARY}.
+ *
+ * @param callback Callback that will manage lifecycle events for this action mode
+ * @return The ActionMode that was started, or null if it was canceled
+ *
+ * @see ActionMode
+ */
+ @Nullable
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return mWindow.getDecorView().startActionMode(callback);
+ }
+
+ /**
+ * Start an action mode of the given type.
+ *
+ * @param callback Callback that will manage lifecycle events for this action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The ActionMode that was started, or null if it was canceled
+ *
+ * @see ActionMode
+ */
+ @Nullable
+ public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+ return mWindow.getDecorView().startActionMode(callback, type);
+ }
+
+ /**
+ * Give the Activity a chance to control the UI for an action mode requested
+ * by the system.
+ *
+ * <p>Note: If you are looking for a notification callback that an action mode
+ * has been started for this activity, see {@link #onActionModeStarted(ActionMode)}.</p>
+ *
+ * @param callback The callback that should control the new action mode
+ * @return The new action mode, or <code>null</code> if the activity does not want to
+ * provide special handling for this action mode. (It will be handled by the system.)
+ */
+ @Nullable
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+ // Only Primary ActionModes are represented in the ActionBar.
+ if (mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) {
+ initWindowDecorActionBar();
+ if (mActionBar != null) {
+ return mActionBar.startActionMode(callback);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Nullable
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+ try {
+ mActionModeTypeStarting = type;
+ return onWindowStartingActionMode(callback);
+ } finally {
+ mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+ }
+ }
+
+ /**
+ * Notifies the Activity that an action mode has been started.
+ * Activity subclasses overriding this method should call the superclass implementation.
+ *
+ * @param mode The new action mode.
+ */
+ @CallSuper
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ }
+
+ /**
+ * Notifies the activity that an action mode has finished.
+ * Activity subclasses overriding this method should call the superclass implementation.
+ *
+ * @param mode The action mode that just finished.
+ */
+ @CallSuper
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ }
+
+ /**
+ * Returns true if the app should recreate the task when navigating 'up' from this activity
+ * by using targetIntent.
+ *
+ * <p>If this method returns false the app can trivially call
+ * {@link #navigateUpTo(Intent)} using the same parameters to correctly perform
+ * up navigation. If this method returns false, the app should synthesize a new task stack
+ * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p>
+ *
+ * @param targetIntent An intent representing the target destination for up navigation
+ * @return true if navigating up should recreate a new task stack, false if the same task
+ * should be used for the destination
+ */
+ public boolean shouldUpRecreateTask(Intent targetIntent) {
+ try {
+ PackageManager pm = getPackageManager();
+ ComponentName cn = targetIntent.getComponent();
+ if (cn == null) {
+ cn = targetIntent.resolveActivity(pm);
+ }
+ ActivityInfo info = pm.getActivityInfo(cn, 0);
+ if (info.taskAffinity == null) {
+ return false;
+ }
+ return ActivityClient.getInstance().shouldUpRecreateTask(mToken, info.taskAffinity);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Navigate from this activity to the activity specified by upIntent, finishing this activity
+ * in the process. If the activity indicated by upIntent already exists in the task's history,
+ * this activity and all others before the indicated activity in the history stack will be
+ * finished.
+ *
+ * <p>If the indicated activity does not appear in the history stack, this will finish
+ * each activity in this task until the root activity of the task is reached, resulting in
+ * an "in-app home" behavior. This can be useful in apps with a complex navigation hierarchy
+ * when an activity may be reached by a path not passing through a canonical parent
+ * activity.</p>
+ *
+ * <p>This method should be used when performing up navigation from within the same task
+ * as the destination. If up navigation should cross tasks in some cases, see
+ * {@link #shouldUpRecreateTask(Intent)}.</p>
+ *
+ * @param upIntent An intent representing the target destination for up navigation
+ *
+ * @return true if up navigation successfully reached the activity indicated by upIntent and
+ * upIntent was delivered to it. false if an instance of the indicated activity could
+ * not be found and this activity was simply finished normally.
+ */
+ public boolean navigateUpTo(Intent upIntent) {
+ if (mParent == null) {
+ ComponentName destInfo = upIntent.getComponent();
+ if (destInfo == null) {
+ destInfo = upIntent.resolveActivity(getPackageManager());
+ if (destInfo == null) {
+ return false;
+ }
+ upIntent = new Intent(upIntent);
+ upIntent.setComponent(destInfo);
+ }
+ int resultCode;
+ Intent resultData;
+ synchronized (this) {
+ resultCode = mResultCode;
+ resultData = mResultData;
+ }
+ if (resultData != null) {
+ resultData.prepareToLeaveProcess(this);
+ }
+ upIntent.prepareToLeaveProcess(this);
+ String resolvedType = upIntent.resolveTypeIfNeeded(getContentResolver());
+ return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resolvedType,
+ resultCode, resultData);
+ } else {
+ return mParent.navigateUpToFromChild(this, upIntent);
+ }
+ }
+
+ /**
+ * This is called when a child activity of this one calls its
+ * {@link #navigateUpTo} method. The default implementation simply calls
+ * navigateUpTo(upIntent) on this activity (the parent).
+ *
+ * @param child The activity making the call.
+ * @param upIntent An intent representing the target destination for up navigation
+ *
+ * @return true if up navigation successfully reached the activity indicated by upIntent and
+ * upIntent was delivered to it. false if an instance of the indicated activity could
+ * not be found and this activity was simply finished normally.
+ * @deprecated Use {@link #navigateUpTo(Intent)} instead.
+ */
+ @Deprecated
+ public boolean navigateUpToFromChild(Activity child, Intent upIntent) {
+ return navigateUpTo(upIntent);
+ }
+
+ /**
+ * Obtain an {@link Intent} that will launch an explicit target activity specified by
+ * this activity's logical parent. The logical parent is named in the application's manifest
+ * by the {@link android.R.attr#parentActivityName parentActivityName} attribute.
+ * Activity subclasses may override this method to modify the Intent returned by
+ * super.getParentActivityIntent() or to implement a different mechanism of retrieving
+ * the parent intent entirely.
+ *
+ * @return a new Intent targeting the defined parent of this activity or null if
+ * there is no valid parent.
+ */
+ @Nullable
+ public Intent getParentActivityIntent() {
+ final String parentName = mActivityInfo.parentActivityName;
+ if (TextUtils.isEmpty(parentName)) {
+ return null;
+ }
+
+ // If the parent itself has no parent, generate a main activity intent.
+ final ComponentName target = new ComponentName(this, parentName);
+ try {
+ final ActivityInfo parentInfo = getPackageManager().getActivityInfo(target, 0);
+ final String parentActivity = parentInfo.parentActivityName;
+ final Intent parentIntent = parentActivity == null
+ ? Intent.makeMainActivity(target)
+ : new Intent().setComponent(target);
+ return parentIntent;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "getParentActivityIntent: bad parentActivityName '" + parentName +
+ "' in manifest");
+ return null;
+ }
+ }
+
+ /**
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.view.View, String)} was used to start an Activity, <var>callback</var>
+ * will be called to handle shared elements on the <i>launched</i> Activity. This requires
+ * {@link Window#FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param callback Used to manipulate shared element transitions on the launched Activity.
+ */
+ public void setEnterSharedElementCallback(SharedElementCallback callback) {
+ if (callback == null) {
+ callback = SharedElementCallback.NULL_CALLBACK;
+ }
+ mEnterTransitionListener = callback;
+ }
+
+ /**
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.view.View, String)} was used to start an Activity, <var>callback</var>
+ * will be called to handle shared elements on the <i>launching</i> Activity. Most
+ * calls will only come when returning from the started Activity.
+ * This requires {@link Window#FEATURE_ACTIVITY_TRANSITIONS}.
+ *
+ * @param callback Used to manipulate shared element transitions on the launching Activity.
+ */
+ public void setExitSharedElementCallback(SharedElementCallback callback) {
+ if (callback == null) {
+ callback = SharedElementCallback.NULL_CALLBACK;
+ }
+ mExitTransitionListener = callback;
+ }
+
+ /**
+ * Postpone the entering activity transition when Activity was started with
+ * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.util.Pair[])}.
+ * <p>This method gives the Activity the ability to delay starting the entering and
+ * shared element transitions until all data is loaded. Until then, the Activity won't
+ * draw into its window, leaving the window transparent. This may also cause the
+ * returning animation to be delayed until data is ready. This method should be
+ * called in {@link #onCreate(android.os.Bundle)} or in
+ * {@link #onActivityReenter(int, android.content.Intent)}.
+ * {@link #startPostponedEnterTransition()} must be called to allow the Activity to
+ * start the transitions. If the Activity did not use
+ * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.util.Pair[])}, then this method does nothing.</p>
+ */
+ public void postponeEnterTransition() {
+ mActivityTransitionState.postponeEnterTransition();
+ }
+
+ /**
+ * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+ * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+ * to have your Activity start drawing.
+ */
+ public void startPostponedEnterTransition() {
+ mActivityTransitionState.startPostponedEnterTransition();
+ }
+
+ /**
+ * Create {@link DragAndDropPermissions} object bound to this activity and controlling the
+ * access permissions for content URIs associated with the {@link DragEvent}.
+ * @param event Drag event
+ * @return The {@link DragAndDropPermissions} object used to control access to the content URIs.
+ * Null if no content URIs are associated with the event or if permissions could not be granted.
+ */
+ public DragAndDropPermissions requestDragAndDropPermissions(DragEvent event) {
+ DragAndDropPermissions dragAndDropPermissions = DragAndDropPermissions.obtain(event);
+ if (dragAndDropPermissions != null && dragAndDropPermissions.take(getActivityToken())) {
+ return dragAndDropPermissions;
+ }
+ return null;
+ }
+
+ // ------------------ Internal API ------------------
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final void setParent(Activity parent) {
+ mParent = parent;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final void attach(Context context, ActivityThread aThread,
+ Instrumentation instr, IBinder token, int ident,
+ Application application, Intent intent, ActivityInfo info,
+ CharSequence title, Activity parent, String id,
+ NonConfigurationInstances lastNonConfigurationInstances,
+ Configuration config, String referrer, IVoiceInteractor voiceInteractor,
+ Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
+ IBinder shareableActivityToken) {
+ attachBaseContext(context);
+
+ mFragments.attachHost(null /*parent*/);
+ mActivityInfo = info;
+
+ mWindow = new PhoneWindow(this, window, activityConfigCallback);
+ mWindow.setWindowControllerCallback(mWindowControllerCallback);
+ mWindow.setCallback(this);
+ mWindow.setOnWindowDismissedCallback(this);
+ mWindow.getLayoutInflater().setPrivateFactory(this);
+ if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+ mWindow.setSoftInputMode(info.softInputMode);
+ }
+ if (info.uiOptions != 0) {
+ mWindow.setUiOptions(info.uiOptions);
+ }
+ mUiThread = Thread.currentThread();
+
+ mMainThread = aThread;
+ mInstrumentation = instr;
+ mToken = token;
+ mAssistToken = assistToken;
+ mShareableActivityToken = shareableActivityToken;
+ mIdent = ident;
+ mApplication = application;
+ mIntent = intent;
+ mReferrer = referrer;
+ mComponent = intent.getComponent();
+ mTitle = title;
+ mParent = parent;
+ mEmbeddedID = id;
+ mLastNonConfigurationInstances = lastNonConfigurationInstances;
+ if (voiceInteractor != null) {
+ if (lastNonConfigurationInstances != null) {
+ mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
+ } else {
+ mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+ Looper.myLooper());
+ }
+ }
+
+ mWindow.setWindowManager(
+ (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
+ mToken, mComponent.flattenToString(),
+ (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
+ if (mParent != null) {
+ mWindow.setContainer(mParent.getWindow());
+ }
+ mWindowManager = mWindow.getWindowManager();
+ mCurrentConfig = config;
+
+ mWindow.setColorMode(info.colorMode);
+ mWindow.setPreferMinimalPostProcessing(
+ (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);
+
+ getAutofillClientController().onActivityAttached(application);
+ setContentCaptureOptions(application.getContentCaptureOptions());
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public final IBinder getActivityToken() {
+ return mParent != null ? mParent.getActivityToken() : mToken;
+ }
+
+ /** @hide */
+ public final IBinder getAssistToken() {
+ return mParent != null ? mParent.getAssistToken() : mAssistToken;
+ }
+
+ /** @hide */
+ public final IBinder getShareableActivityToken() {
+ return mParent != null ? mParent.getShareableActivityToken() : mShareableActivityToken;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public final ActivityThread getActivityThread() {
+ return mMainThread;
+ }
+
+ /** @hide */
+ public final ActivityInfo getActivityInfo() {
+ return mActivityInfo;
+ }
+
+ final void performCreate(Bundle icicle) {
+ performCreate(icicle, null);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performCreate:"
+ + mComponent.getClassName());
+ }
+ dispatchActivityPreCreated(icicle);
+ mCanEnterPictureInPicture = true;
+ // initialize mIsInMultiWindowMode and mIsInPictureInPictureMode before onCreate
+ final int windowingMode = getResources().getConfiguration().windowConfiguration
+ .getWindowingMode();
+ mIsInMultiWindowMode = inMultiWindowMode(windowingMode);
+ mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
+ mShouldDockBigOverlays = getResources().getBoolean(R.bool.config_dockBigOverlayWindows);
+ restoreHasCurrentPermissionRequest(icicle);
+ final long startTime = SystemClock.uptimeMillis();
+ if (persistentState != null) {
+ onCreate(icicle, persistentState);
+ } else {
+ onCreate(icicle);
+ }
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnCreateCalled(mIdent, getComponentName().getClassName(),
+ "performCreate", duration);
+ mActivityTransitionState.readState(icicle);
+
+ mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
+ com.android.internal.R.styleable.Window_windowNoDisplay, false);
+ mFragments.dispatchActivityCreated();
+ mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ dispatchActivityPostCreated(icicle);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void performNewIntent(@NonNull Intent intent) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performNewIntent");
+ mCanEnterPictureInPicture = true;
+ onNewIntent(intent);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void performStart(String reason) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:"
+ + mComponent.getClassName());
+ }
+ dispatchActivityPreStarted();
+ mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ mFragments.noteStateNotSaved();
+ mCalled = false;
+ mFragments.execPendingActions();
+ final long startTime = SystemClock.uptimeMillis();
+ mInstrumentation.callActivityOnStart(this);
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason,
+ duration);
+
+ if (!mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onStart()");
+ }
+ mFragments.dispatchStart();
+ mFragments.reportLoaderStart();
+
+ // Warn app developers if the dynamic linker logged anything during startup.
+ boolean isAppDebuggable =
+ (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (isAppDebuggable) {
+ String dlwarning = getDlWarning();
+ if (dlwarning != null) {
+ String appName = getApplicationInfo().loadLabel(getPackageManager())
+ .toString();
+ String warning = "Detected problems with app native libraries\n" +
+ "(please consult log for detail):\n" + dlwarning;
+ if (isAppDebuggable) {
+ new AlertDialog.Builder(this).
+ setTitle(appName).
+ setMessage(warning).
+ setPositiveButton(android.R.string.ok, null).
+ setCancelable(false).
+ show();
+ } else {
+ Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ GraphicsEnvironment.getInstance().showAngleInUseDialogBox(this);
+
+ mActivityTransitionState.enterReady(this);
+ dispatchActivityPostStarted();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ /**
+ * Restart the activity.
+ * @param start Indicates whether the activity should also be started after restart.
+ * The option to not start immediately is needed in case a transaction with
+ * multiple lifecycle transitions is in progress.
+ */
+ final void performRestart(boolean start) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performRestart");
+ mCanEnterPictureInPicture = true;
+ mFragments.noteStateNotSaved();
+
+ if (mToken != null && mParent == null) {
+ // No need to check mStopped, the roots will check if they were actually stopped.
+ WindowManagerGlobal.getInstance().setStoppedState(mToken, false /* stopped */);
+ }
+
+ if (mStopped) {
+ mStopped = false;
+
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (mc.mReleased || mc.mUpdated) {
+ if (!mc.mCursor.requery()) {
+ if (getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ throw new IllegalStateException(
+ "trying to requery an already closed cursor "
+ + mc.mCursor);
+ }
+ }
+ mc.mReleased = false;
+ mc.mUpdated = false;
+ }
+ }
+ }
+
+ mCalled = false;
+ final long startTime = SystemClock.uptimeMillis();
+ mInstrumentation.callActivityOnRestart(this);
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(),
+ "performRestart", duration);
+ if (!mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onRestart()");
+ }
+ if (start) {
+ performStart("performRestart");
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void performResume(boolean followedByPause, String reason) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performResume:"
+ + mComponent.getClassName());
+ }
+ dispatchActivityPreResumed();
+
+ mFragments.execPendingActions();
+
+ mLastNonConfigurationInstances = null;
+
+ getAutofillClientController().onActivityPerformResume(followedByPause);
+
+ mCalled = false;
+ final long startTime = SystemClock.uptimeMillis();
+ // mResumed is set by the instrumentation
+ mInstrumentation.callActivityOnResume(this);
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason,
+ duration);
+ if (!mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onResume()");
+ }
+
+ // invisible activities must be finished before onResume) completes
+ if (!mVisibleFromClient && !mFinished) {
+ Log.w(TAG, "An activity without a UI must call finish() before onResume() completes");
+ if (getApplicationInfo().targetSdkVersion
+ > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
+ throw new IllegalStateException(
+ "Activity " + mComponent.toShortString() +
+ " did not call finish() prior to onResume() completing");
+ }
+ }
+
+ // Now really resume, and install the current status bar and menu.
+ mCalled = false;
+
+ mFragments.dispatchResume();
+ mFragments.execPendingActions();
+
+ onPostResume();
+ if (!mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onPostResume()");
+ }
+ dispatchActivityPostResumed();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void performPause() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:"
+ + mComponent.getClassName());
+ }
+ dispatchActivityPrePaused();
+ mDoReportFullyDrawn = false;
+ mFragments.dispatchPause();
+ mCalled = false;
+ final long startTime = SystemClock.uptimeMillis();
+ onPause();
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnPausedCalled(mIdent, getComponentName().getClassName(),
+ "performPause", duration);
+ mResumed = false;
+ if (!mCalled && getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.GINGERBREAD) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onPause()");
+ }
+ dispatchActivityPostPaused();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void performUserLeaving() {
+ onUserInteraction();
+ onUserLeaveHint();
+ }
+
+ final void performStop(boolean preserveWindow, String reason) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStop:"
+ + mComponent.getClassName());
+ }
+ mDoReportFullyDrawn = false;
+ mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
+
+ // Disallow entering picture-in-picture after the activity has been stopped
+ mCanEnterPictureInPicture = false;
+
+ if (!mStopped) {
+ dispatchActivityPreStopped();
+ if (mWindow != null) {
+ mWindow.closeAllPanels();
+ }
+
+ // If we're preserving the window, don't setStoppedState to true, since we
+ // need the window started immediately again. Stopping the window will
+ // destroys hardware resources and causes flicker.
+ if (!preserveWindow && mToken != null && mParent == null) {
+ WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
+ }
+
+ mFragments.dispatchStop();
+
+ mCalled = false;
+ final long startTime = SystemClock.uptimeMillis();
+ mInstrumentation.callActivityOnStop(this);
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnStopCalled(mIdent, getComponentName().getClassName(), reason,
+ duration);
+ if (!mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onStop()");
+ }
+
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (!mc.mReleased) {
+ mc.mCursor.deactivate();
+ mc.mReleased = true;
+ }
+ }
+ }
+
+ mStopped = true;
+ dispatchActivityPostStopped();
+ }
+ mResumed = false;
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void performDestroy() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performDestroy:"
+ + mComponent.getClassName());
+ }
+ dispatchActivityPreDestroyed();
+ mDestroyed = true;
+ mWindow.destroy();
+ mFragments.dispatchDestroy();
+ final long startTime = SystemClock.uptimeMillis();
+ onDestroy();
+ final long duration = SystemClock.uptimeMillis() - startTime;
+ EventLogTags.writeWmOnDestroyCalled(mIdent, getComponentName().getClassName(),
+ "performDestroy", duration);
+ mFragments.doLoaderDestroy();
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.detachActivity();
+ }
+ dispatchActivityPostDestroyed();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode,
+ Configuration newConfig) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG,
+ "dispatchMultiWindowModeChanged " + this + ": " + isInMultiWindowMode
+ + " " + newConfig);
+ mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+ if (mWindow != null) {
+ mWindow.onMultiWindowModeChanged();
+ }
+ mIsInMultiWindowMode = isInMultiWindowMode;
+ onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+ }
+
+ final void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+ Configuration newConfig) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG,
+ "dispatchPictureInPictureModeChanged " + this + ": " + isInPictureInPictureMode
+ + " " + newConfig);
+ mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+ if (mWindow != null) {
+ mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ mIsInPictureInPictureMode = isInPictureInPictureMode;
+ onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final boolean isResumed() {
+ return mResumed;
+ }
+
+ private void storeHasCurrentPermissionRequest(Bundle bundle) {
+ if (bundle != null && mHasCurrentPermissionsRequest) {
+ bundle.putBoolean(HAS_CURENT_PERMISSIONS_REQUEST_KEY, true);
+ }
+ }
+
+ private void restoreHasCurrentPermissionRequest(Bundle bundle) {
+ if (bundle != null) {
+ mHasCurrentPermissionsRequest = bundle.getBoolean(
+ HAS_CURENT_PERMISSIONS_REQUEST_KEY, false);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ void dispatchActivityResult(String who, int requestCode, int resultCode, Intent data,
+ String reason) {
+ if (false) Log.v(
+ TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ + ", resCode=" + resultCode + ", data=" + data);
+ mFragments.noteStateNotSaved();
+ if (who == null) {
+ onActivityResult(requestCode, resultCode, data);
+ } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
+ who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
+ if (TextUtils.isEmpty(who)) {
+ dispatchRequestPermissionsResult(requestCode, data);
+ } else {
+ Fragment frag = mFragments.findFragmentByWho(who);
+ if (frag != null) {
+ dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
+ }
+ }
+ } else if (who.startsWith("@android:view:")) {
+ ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
+ getActivityToken());
+ for (ViewRootImpl viewRoot : views) {
+ if (viewRoot.getView() != null
+ && viewRoot.getView().dispatchActivityResult(
+ who, requestCode, resultCode, data)) {
+ return;
+ }
+ }
+ } else if (who.startsWith(AutofillClientController.AUTO_FILL_AUTH_WHO_PREFIX)) {
+ getAutofillClientController().onDispatchActivityResult(requestCode, resultCode, data);
+ } else {
+ Fragment frag = mFragments.findFragmentByWho(who);
+ if (frag != null) {
+ frag.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ EventLogTags.writeWmOnActivityResultCalled(mIdent, getComponentName().getClassName(),
+ reason);
+ }
+
+ /**
+ * Request to put this activity in a mode where the user is locked to a restricted set of
+ * applications.
+ *
+ * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+ * for this component, the current task will be launched directly into LockTask mode. Only apps
+ * allowlisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+ * be launched while LockTask mode is active. The user will not be able to leave this mode
+ * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+ * already in LockTask mode has no effect.
+ *
+ * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+ * system will prompt the user with a dialog requesting permission to use this mode.
+ * The user can exit at any time through instructions shown on the request dialog. Calling
+ * {@link #stopLockTask()} will also terminate this mode.
+ *
+ * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+ * That is, between {@link #onResume()} and {@link #onPause()}.
+ *
+ * @see #stopLockTask()
+ * @see android.R.attr#lockTaskMode
+ */
+ public void startLockTask() {
+ ActivityClient.getInstance().startLockTaskModeByToken(mToken);
+ }
+
+ /**
+ * Stop the current task from being locked.
+ *
+ * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+ * This can only be called by activities that have called {@link #startLockTask()} previously.
+ *
+ * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+ * by this activity, then calling this method will not terminate the LockTask mode, but only
+ * finish its own task. The device will remain in LockTask mode, until the activity which
+ * started the LockTask mode calls this method, or until its allowlist authorization is revoked
+ * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
+ *
+ * @see #startLockTask()
+ * @see android.R.attr#lockTaskMode
+ * @see ActivityManager#getLockTaskModeState()
+ */
+ public void stopLockTask() {
+ ActivityClient.getInstance().stopLockTaskModeByToken(mToken);
+ }
+
+ /**
+ * Shows the user the system defined message for telling the user how to exit
+ * lock task mode. The task containing this activity must be in lock task mode at the time
+ * of this call for the message to be displayed.
+ */
+ public void showLockTaskEscapeMessage() {
+ ActivityClient.getInstance().showLockTaskEscapeMessage(mToken);
+ }
+
+ /**
+ * Check whether the caption on freeform windows is displayed directly on the content.
+ *
+ * @return True if caption is displayed on content, false if it pushes the content down.
+ *
+ * @see #setOverlayWithDecorCaptionEnabled(boolean)
+ * @hide
+ */
+ public boolean isOverlayWithDecorCaptionEnabled() {
+ return mWindow.isOverlayWithDecorCaptionEnabled();
+ }
+
+ /**
+ * Set whether the caption should displayed directly on the content rather than push it down.
+ *
+ * This affects only freeform windows since they display the caption and only the main
+ * window of the activity. The caption is used to drag the window around and also shows
+ * maximize and close action buttons.
+ * @hide
+ */
+ public void setOverlayWithDecorCaptionEnabled(boolean enabled) {
+ mWindow.setOverlayWithDecorCaptionEnabled(enabled);
+ }
+
+ /**
+ * Interface for informing a translucent {@link Activity} once all visible activities below it
+ * have completed drawing. This is necessary only after an {@link Activity} has been made
+ * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
+ * translucent again following a call to {@link
+ * Activity#convertToTranslucent(android.app.Activity.TranslucentConversionListener,
+ * ActivityOptions)}
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface TranslucentConversionListener {
+ /**
+ * Callback made following {@link Activity#convertToTranslucent} once all visible Activities
+ * below the top one have been redrawn. Following this callback it is safe to make the top
+ * Activity translucent because the underlying Activity has been drawn.
+ *
+ * @param drawComplete True if the background Activity has drawn itself. False if a timeout
+ * occurred waiting for the Activity to complete drawing.
+ *
+ * @see Activity#convertFromTranslucent()
+ * @see Activity#convertToTranslucent(TranslucentConversionListener, ActivityOptions)
+ */
+ public void onTranslucentConversionComplete(boolean drawComplete);
+ }
+
+ private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
+ mHasCurrentPermissionsRequest = false;
+ // If the package installer crashed we may have not data - best effort.
+ String[] permissions = (data != null) ? data.getStringArrayExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
+ final int[] grantResults = (data != null) ? data.getIntArrayExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
+ onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ private void dispatchRequestPermissionsResultToFragment(int requestCode, Intent data,
+ Fragment fragment) {
+ // If the package installer crashed we may have not data - best effort.
+ String[] permissions = (data != null) ? data.getStringArrayExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
+ final int[] grantResults = (data != null) ? data.getIntArrayExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
+ fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ /**
+ * @hide
+ */
+ public final boolean isVisibleForAutofill() {
+ return !mStopped;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S,
+ publicAlternatives = "Use {@link #setRecentsScreenshotEnabled(boolean)} instead.")
+ public void setDisablePreviewScreenshots(boolean disable) {
+ setRecentsScreenshotEnabled(!disable);
+ }
+
+ /**
+ * If set to false, this indicates to the system that it should never take a
+ * screenshot of the activity to be used as a representation in recents screen. By default, this
+ * value is {@code true}.
+ * <p>
+ * Note that the system may use the window background of the theme instead to represent
+ * the window when it is not running.
+ * <p>
+ * Also note that in comparison to {@link android.view.WindowManager.LayoutParams#FLAG_SECURE},
+ * this only affects the behavior when the activity's screenshot would be used as a
+ * representation when the activity is not in a started state, i.e. in Overview. The system may
+ * still take screenshots of the activity in other contexts; for example, when the user takes a
+ * screenshot of the entire screen, or when the active
+ * {@link android.service.voice.VoiceInteractionService} requests a screenshot via
+ * {@link android.service.voice.VoiceInteractionSession#SHOW_WITH_SCREENSHOT}.
+ *
+ * @param enabled {@code true} to enable recents screenshots; {@code false} otherwise.
+ */
+ public void setRecentsScreenshotEnabled(boolean enabled) {
+ ActivityClient.getInstance().setRecentsScreenshotEnabled(mToken, enabled);
+ }
+
+ /**
+ * Specifies whether an {@link Activity} should be shown on top of the lock screen whenever
+ * the lockscreen is up and the activity is resumed. Normally an activity will be transitioned
+ * to the stopped state if it is started while the lockscreen is up, but with this flag set the
+ * activity will remain in the resumed state visible on-top of the lock screen. This value can
+ * be set as a manifest attribute using {@link android.R.attr#showWhenLocked}.
+ *
+ * @param showWhenLocked {@code true} to show the {@link Activity} on top of the lock screen;
+ * {@code false} otherwise.
+ * @see #setTurnScreenOn(boolean)
+ * @see android.R.attr#turnScreenOn
+ * @see android.R.attr#showWhenLocked
+ */
+ public void setShowWhenLocked(boolean showWhenLocked) {
+ ActivityClient.getInstance().setShowWhenLocked(mToken, showWhenLocked);
+ }
+
+ /**
+ * Specifies whether this {@link Activity} should be shown on top of the lock screen whenever
+ * the lockscreen is up and this activity has another activity behind it with the showWhenLock
+ * attribute set. That is, this activity is only visible on the lock screen if there is another
+ * activity with the showWhenLock attribute visible at the same time on the lock screen. A use
+ * case for this is permission dialogs, that should only be visible on the lock screen if their
+ * requesting activity is also visible. This value can be set as a manifest attribute using
+ * android.R.attr#inheritShowWhenLocked.
+ *
+ * @param inheritShowWhenLocked {@code true} to show the {@link Activity} on top of the lock
+ * screen when this activity has another activity behind it with
+ * the showWhenLock attribute set; {@code false} otherwise.
+ * @see #setShowWhenLocked(boolean)
+ * @see android.R.attr#inheritShowWhenLocked
+ */
+ public void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
+ ActivityClient.getInstance().setInheritShowWhenLocked(mToken, inheritShowWhenLocked);
+ }
+
+ /**
+ * Specifies whether the screen should be turned on when the {@link Activity} is resumed.
+ * Normally an activity will be transitioned to the stopped state if it is started while the
+ * screen if off, but with this flag set the activity will cause the screen to turn on if the
+ * activity will be visible and resumed due to the screen coming on. The screen will not be
+ * turned on if the activity won't be visible after the screen is turned on. This flag is
+ * normally used in conjunction with the {@link android.R.attr#showWhenLocked} flag to make sure
+ * the activity is visible after the screen is turned on when the lockscreen is up. In addition,
+ * if this flag is set and the activity calls {@link
+ * KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)}
+ * the screen will turn on.
+ *
+ * @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise.
+ *
+ * @see #setShowWhenLocked(boolean)
+ * @see android.R.attr#turnScreenOn
+ * @see android.R.attr#showWhenLocked
+ * @see KeyguardManager#isDeviceSecure()
+ */
+ public void setTurnScreenOn(boolean turnScreenOn) {
+ ActivityClient.getInstance().setTurnScreenOn(mToken, turnScreenOn);
+ }
+
+ /**
+ * Specifies whether the activities below this one in the task can also start other activities
+ * or finish the task.
+ * <p>
+ * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, apps
+ * are blocked from starting new activities or finishing their task unless the top activity of
+ * such task belong to the same UID for security reasons.
+ * <p>
+ * Setting this flag to {@code true} will allow the launching app to ignore the restriction if
+ * this activity is on top. Apps matching the UID of this activity are always exempt.
+ *
+ * @param allowed {@code true} to disable the UID restrictions; {@code false} to revert back to
+ * the default behaviour
+ * @hide
+ */
+ public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) {
+ ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed);
+ }
+
+ /**
+ * Registers remote animations per transition type for this activity.
+ *
+ * @param definition The remote animation definition that defines which transition whould run
+ * which remote animation.
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+ ActivityClient.getInstance().registerRemoteAnimations(mToken, definition);
+ }
+
+ /**
+ * Unregisters all remote animations for this activity.
+ *
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void unregisterRemoteAnimations() {
+ ActivityClient.getInstance().unregisterRemoteAnimations(mToken);
+ }
+
+ /**
+ * Notify {@link UiTranslationController} the ui translation state is changed.
+ * @hide
+ */
+ public void updateUiTranslationState(int state, TranslationSpec sourceSpec,
+ TranslationSpec targetSpec, List<AutofillId> viewIds,
+ UiTranslationSpec uiTranslationSpec) {
+ if (mUiTranslationController == null) {
+ mUiTranslationController = new UiTranslationController(this, getApplicationContext());
+ }
+ mUiTranslationController.updateUiTranslationState(
+ state, sourceSpec, targetSpec, viewIds, uiTranslationSpec);
+ }
+
+ /**
+ * If set, any activity launch in the same task will be overridden to the locale of activity
+ * that started the task.
+ *
+ * <p>Currently, Android supports per app languages, and system apps are able to start
+ * activities of another package on the same task, which may cause users to set different
+ * languages in different apps and display two different languages in one app.</p>
+ *
+ * <p>The <a href="https://developer.android.com/guide/topics/large-screens/activity-embedding">
+ * activity embedding feature</a> will align the locale with root activity automatically, but
+ * it doesn't land on the phone yet. If activity embedding land on the phone in the future,
+ * please consider adapting activity embedding directly.</p>
+ *
+ * @hide
+ */
+ public void enableTaskLocaleOverride() {
+ ActivityClient.getInstance().enableTaskLocaleOverride(mToken);
+ }
+
+ class HostCallbacks extends FragmentHostCallback<Activity> {
+ public HostCallbacks() {
+ super(Activity.this /*activity*/);
+ }
+
+ @Override
+ public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ Activity.this.dump(prefix, fd, writer, args);
+ }
+
+ @Override
+ public boolean onShouldSaveFragmentState(Fragment fragment) {
+ return !isFinishing();
+ }
+
+ @Override
+ public LayoutInflater onGetLayoutInflater() {
+ final LayoutInflater result = Activity.this.getLayoutInflater();
+ if (onUseFragmentManagerInflaterFactory()) {
+ return result.cloneInContext(Activity.this);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onUseFragmentManagerInflaterFactory() {
+ // Newer platform versions use the child fragment manager's LayoutInflaterFactory.
+ return getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
+ }
+
+ @Override
+ public Activity onGetHost() {
+ return Activity.this;
+ }
+
+ @Override
+ public void onInvalidateOptionsMenu() {
+ Activity.this.invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode,
+ Bundle options) {
+ Activity.this.startActivityFromFragment(fragment, intent, requestCode, options);
+ }
+
+ @Override
+ public void onStartActivityAsUserFromFragment(
+ Fragment fragment, Intent intent, int requestCode, Bundle options,
+ UserHandle user) {
+ Activity.this.startActivityAsUserFromFragment(
+ fragment, intent, requestCode, options, user);
+ }
+
+ @Override
+ public void onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, Bundle options) throws IntentSender.SendIntentException {
+ if (mParent == null) {
+ startIntentSenderForResultInner(intent, fragment.mWho, requestCode, fillInIntent,
+ flagsMask, flagsValues, options);
+ } else if (options != null) {
+ mParent.startIntentSenderFromFragment(fragment, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, options);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsFromFragment(Fragment fragment, String[] permissions,
+ int requestCode) {
+ String who = REQUEST_PERMISSIONS_WHO_PREFIX + fragment.mWho;
+ Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+ startActivityForResult(who, intent, requestCode, null);
+ }
+
+ @Override
+ public boolean onHasWindowAnimations() {
+ return getWindow() != null;
+ }
+
+ @Override
+ public int onGetWindowAnimations() {
+ final Window w = getWindow();
+ return (w == null) ? 0 : w.getAttributes().windowAnimations;
+ }
+
+ @Override
+ public void onAttachFragment(Fragment fragment) {
+ Activity.this.onAttachFragment(fragment);
+ }
+
+ @Nullable
+ @Override
+ public <T extends View> T onFindViewById(int id) {
+ return Activity.this.findViewById(id);
+ }
+
+ @Override
+ public boolean onHasView() {
+ final Window w = getWindow();
+ return (w != null && w.peekDecorView() != null);
+ }
+ }
+
+ /**
+ * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this
+ * activity is attached to.
+ *
+ * @throws IllegalStateException if this Activity is not visual.
+ */
+ @NonNull
+ public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+ if (mWindow == null) {
+ throw new IllegalStateException("OnBackInvokedDispatcher are not available on "
+ + "non-visual activities");
+ }
+ return mWindow.getOnBackInvokedDispatcher();
+ }
+
+ /**
+ * Interface for observing screen captures of an {@link Activity}.
+ */
+ public interface ScreenCaptureCallback {
+ /**
+ * Called when one of the monitored activities is captured.
+ * This is not invoked if the activity window
+ * has {@link WindowManager.LayoutParams#FLAG_SECURE} set.
+ */
+ void onScreenCaptured();
+ }
+
+ /**
+ * Registers a screen capture callback for this activity.
+ * The callback will be triggered when a screen capture of this activity is attempted.
+ * This callback will be executed on the thread of the passed {@code executor}.
+ * For details, see {@link ScreenCaptureCallback#onScreenCaptured}.
+ */
+ @RequiresPermission(DETECT_SCREEN_CAPTURE)
+ public void registerScreenCaptureCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ScreenCaptureCallback callback) {
+ if (mScreenCaptureCallbackHandler == null) {
+ mScreenCaptureCallbackHandler = new ScreenCaptureCallbackHandler(mToken);
+ }
+ mScreenCaptureCallbackHandler.registerScreenCaptureCallback(executor, callback);
+ }
+
+
+ /**
+ * Unregisters a screen capture callback for this surface.
+ */
+ @RequiresPermission(DETECT_SCREEN_CAPTURE)
+ public void unregisterScreenCaptureCallback(@NonNull ScreenCaptureCallback callback) {
+ if (mScreenCaptureCallbackHandler != null) {
+ mScreenCaptureCallbackHandler.unregisterScreenCaptureCallback(callback);
+ }
+ }
+}
diff --git a/android-34/android/app/ActivityClient.java b/android-34/android/app/ActivityClient.java
new file mode 100644
index 0000000..b35e87b
--- /dev/null
+++ b/android-34/android/app/ActivityClient.java
@@ -0,0 +1,686 @@
+/*
+ * 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.app;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.util.Singleton;
+import android.view.RemoteAnimationDefinition;
+import android.window.SizeConfigurationBuckets;
+
+import com.android.internal.policy.IKeyguardDismissCallback;
+
+/**
+ * Provides the activity associated operations that communicate with system.
+ *
+ * @hide
+ */
+public class ActivityClient {
+ private ActivityClient() {}
+
+ /** Reports the main thread is idle after the activity is resumed. */
+ public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
+ try {
+ getActivityClientController().activityIdle(token, config, stopProfiling);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports {@link Activity#onResume()} is done. */
+ public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
+ try {
+ getActivityClientController().activityResumed(token, handleSplashScreenExit);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports {@link android.app.servertransaction.RefreshCallbackItem} is executed. */
+ public void activityRefreshed(IBinder token) {
+ try {
+ getActivityClientController().activityRefreshed(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reports after {@link Activity#onTopResumedActivityChanged(boolean)} is called for losing the
+ * top most position.
+ */
+ public void activityTopResumedStateLost() {
+ try {
+ getActivityClientController().activityTopResumedStateLost();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports {@link Activity#onPause()} is done. */
+ public void activityPaused(IBinder token) {
+ try {
+ getActivityClientController().activityPaused(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports {@link Activity#onStop()} is done. */
+ public void activityStopped(IBinder token, Bundle state, PersistableBundle persistentState,
+ CharSequence description) {
+ try {
+ getActivityClientController().activityStopped(token, state, persistentState,
+ description);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports {@link Activity#onDestroy()} is done. */
+ public void activityDestroyed(IBinder token) {
+ try {
+ getActivityClientController().activityDestroyed(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports the activity starts local relaunch. */
+ public void activityLocalRelaunch(IBinder token) {
+ try {
+ getActivityClientController().activityLocalRelaunch(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Reports the activity has completed relaunched. */
+ public void activityRelaunched(IBinder token) {
+ try {
+ getActivityClientController().activityRelaunched(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void reportSizeConfigurations(IBinder token, SizeConfigurationBuckets sizeConfigurations) {
+ try {
+ getActivityClientController().reportSizeConfigurations(token, sizeConfigurations);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) {
+ try {
+ return getActivityClientController().moveActivityTaskToBack(token, nonRoot);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean shouldUpRecreateTask(IBinder token, String destAffinity) {
+ try {
+ return getActivityClientController().shouldUpRecreateTask(token, destAffinity);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType, int resultCode,
+ Intent resultData) {
+ try {
+ return getActivityClientController().navigateUpTo(token, destIntent, resolvedType,
+ resultCode, resultData);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean releaseActivityInstance(IBinder token) {
+ try {
+ return getActivityClientController().releaseActivityInstance(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public boolean finishActivity(IBinder token, int resultCode, Intent resultData,
+ int finishTask) {
+ try {
+ return getActivityClientController().finishActivity(token, resultCode, resultData,
+ finishTask);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean finishActivityAffinity(IBinder token) {
+ try {
+ return getActivityClientController().finishActivityAffinity(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void finishSubActivity(IBinder token, String resultWho, int requestCode) {
+ try {
+ getActivityClientController().finishSubActivity(token, resultWho, requestCode);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ void setForceSendResultForMediaProjection(IBinder token) {
+ try {
+ getActivityClientController().setForceSendResultForMediaProjection(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public boolean isTopOfTask(IBinder token) {
+ try {
+ return getActivityClientController().isTopOfTask(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean willActivityBeVisible(IBinder token) {
+ try {
+ return getActivityClientController().willActivityBeVisible(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public int getDisplayId(IBinder token) {
+ try {
+ return getActivityClientController().getDisplayId(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public int getTaskForActivity(IBinder token, boolean onlyRoot) {
+ try {
+ return getActivityClientController().getTaskForActivity(token, onlyRoot);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+ * the task {@link Configuration} cannot be obtained.
+ */
+ @Nullable
+ public Configuration getTaskConfiguration(IBinder activityToken) {
+ try {
+ return getActivityClientController().getTaskConfiguration(activityToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the non-finishing activity token below in the same task if it belongs to the same
+ * process.
+ */
+ @Nullable
+ public IBinder getActivityTokenBelow(IBinder activityToken) {
+ try {
+ return getActivityClientController().getActivityTokenBelow(activityToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ ComponentName getCallingActivity(IBinder token) {
+ try {
+ return getActivityClientController().getCallingActivity(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ String getCallingPackage(IBinder token) {
+ try {
+ return getActivityClientController().getCallingPackage(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public int getLaunchedFromUid(IBinder token) {
+ try {
+ return getActivityClientController().getLaunchedFromUid(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public String getLaunchedFromPackage(IBinder token) {
+ try {
+ return getActivityClientController().getLaunchedFromPackage(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public void setRequestedOrientation(IBinder token, int requestedOrientation) {
+ try {
+ getActivityClientController().setRequestedOrientation(token, requestedOrientation);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ int getRequestedOrientation(IBinder token) {
+ try {
+ return getActivityClientController().getRequestedOrientation(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean convertFromTranslucent(IBinder token) {
+ try {
+ return getActivityClientController().convertFromTranslucent(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean convertToTranslucent(IBinder token, Bundle options) {
+ try {
+ return getActivityClientController().convertToTranslucent(token, options);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void reportActivityFullyDrawn(IBinder token, boolean restoredFromBundle) {
+ try {
+ getActivityClientController().reportActivityFullyDrawn(token, restoredFromBundle);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean isImmersive(IBinder token) {
+ try {
+ return getActivityClientController().isImmersive(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void setImmersive(IBinder token, boolean immersive) {
+ try {
+ getActivityClientController().setImmersive(token, immersive);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean enterPictureInPictureMode(IBinder token, PictureInPictureParams params) {
+ try {
+ return getActivityClientController().enterPictureInPictureMode(token, params);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void setPictureInPictureParams(IBinder token, PictureInPictureParams params) {
+ try {
+ getActivityClientController().setPictureInPictureParams(token, params);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setShouldDockBigOverlays(IBinder token, boolean shouldDockBigOverlays) {
+ try {
+ getActivityClientController().setShouldDockBigOverlays(token, shouldDockBigOverlays);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void toggleFreeformWindowingMode(IBinder token) {
+ try {
+ getActivityClientController().toggleFreeformWindowingMode(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void requestMultiwindowFullscreen(IBinder token, int request, IRemoteCallback callback) {
+ try {
+ getActivityClientController().requestMultiwindowFullscreen(token, request, callback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void startLockTaskModeByToken(IBinder token) {
+ try {
+ getActivityClientController().startLockTaskModeByToken(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void stopLockTaskModeByToken(IBinder token) {
+ try {
+ getActivityClientController().stopLockTaskModeByToken(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void showLockTaskEscapeMessage(IBinder token) {
+ try {
+ getActivityClientController().showLockTaskEscapeMessage(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setTaskDescription(IBinder token, ActivityManager.TaskDescription td) {
+ try {
+ getActivityClientController().setTaskDescription(token, td);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean showAssistFromActivity(IBinder token, Bundle args) {
+ try {
+ return getActivityClientController().showAssistFromActivity(token, args);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean isRootVoiceInteraction(IBinder token) {
+ try {
+ return getActivityClientController().isRootVoiceInteraction(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
+ try {
+ getActivityClientController().startLocalVoiceInteraction(callingActivity, options);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void stopLocalVoiceInteraction(IBinder callingActivity) {
+ try {
+ getActivityClientController().stopLocalVoiceInteraction(callingActivity);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setShowWhenLocked(IBinder token, boolean showWhenLocked) {
+ try {
+ getActivityClientController().setShowWhenLocked(token, showWhenLocked);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setInheritShowWhenLocked(IBinder token, boolean inheritShowWhenLocked) {
+ try {
+ getActivityClientController().setInheritShowWhenLocked(token, inheritShowWhenLocked);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setTurnScreenOn(IBinder token, boolean turnScreenOn) {
+ try {
+ getActivityClientController().setTurnScreenOn(token, turnScreenOn);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setAllowCrossUidActivitySwitchFromBelow(IBinder token, boolean allowed) {
+ try {
+ getActivityClientController().setAllowCrossUidActivitySwitchFromBelow(token, allowed);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ int setVrMode(IBinder token, boolean enabled, ComponentName packageName) {
+ try {
+ return getActivityClientController().setVrMode(token, enabled, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim,
+ int backgroundColor) {
+ try {
+ getActivityClientController().overrideActivityTransition(
+ token, open, enterAnim, exitAnim, backgroundColor);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void clearOverrideActivityTransition(IBinder token, boolean open) {
+ try {
+ getActivityClientController().clearOverrideActivityTransition(token, open);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim,
+ int backgroundColor) {
+ try {
+ getActivityClientController().overridePendingTransition(token, packageName,
+ enterAnim, exitAnim, backgroundColor);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void setRecentsScreenshotEnabled(IBinder token, boolean enabled) {
+ try {
+ getActivityClientController().setRecentsScreenshotEnabled(token, enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the outdated snapshot of the home task.
+ *
+ * @param homeToken The token of the home task, or null if you have the
+ * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS} permission and
+ * want us to find the home task token for you.
+ */
+ public void invalidateHomeTaskSnapshot(IBinder homeToken) {
+ try {
+ getActivityClientController().invalidateHomeTaskSnapshot(homeToken);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
+ CharSequence message) {
+ try {
+ getActivityClientController().dismissKeyguard(token, callback, message);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) {
+ try {
+ getActivityClientController().registerRemoteAnimations(token, definition);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void unregisterRemoteAnimations(IBinder token) {
+ try {
+ getActivityClientController().unregisterRemoteAnimations(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void onBackPressed(IBinder token, IRequestFinishCallback callback) {
+ try {
+ getActivityClientController().onBackPressed(token, callback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reports the splash screen view has attached to client.
+ */
+ void reportSplashScreenAttached(IBinder token) {
+ try {
+ getActivityClientController().splashScreenAttached(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ void enableTaskLocaleOverride(IBinder token) {
+ try {
+ getActivityClientController().enableTaskLocaleOverride(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns {@code true} if the activity was explicitly requested to be launched in the
+ * TaskFragment.
+ *
+ * @param activityToken The token of the Activity.
+ * @param taskFragmentToken The token of the TaskFragment.
+ */
+ public boolean isRequestedToLaunchInTaskFragment(IBinder activityToken,
+ IBinder taskFragmentToken) {
+ try {
+ return getActivityClientController().isRequestedToLaunchInTaskFragment(activityToken,
+ taskFragmentToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
+ *
+ * @param token The token for the window that needs a control.
+ * @param showControl Whether the control should be shown or hidden.
+ * @param transformationApplied Whether the treatment is already applied.
+ * @param callback The callback executed when the user clicks on a control.
+ */
+ void requestCompatCameraControl(Resources res, IBinder token, boolean showControl,
+ boolean transformationApplied, ICompatCameraControlCallback callback) {
+ if (!res.getBoolean(com.android.internal.R.bool
+ .config_isCameraCompatControlForStretchedIssuesEnabled)) {
+ return;
+ }
+ try {
+ getActivityClientController().requestCompatCameraControl(
+ token, showControl, transformationApplied, callback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ public static ActivityClient getInstance() {
+ return sInstance.get();
+ }
+
+ /**
+ * If system server has passed the controller interface, store it so the subsequent access can
+ * speed up.
+ */
+ public static IActivityClientController setActivityClientController(
+ IActivityClientController activityClientController) {
+ // No lock because it is no harm to encounter race condition. The thread safe Singleton#get
+ // will take over that case.
+ return INTERFACE_SINGLETON.mKnownInstance = activityClientController;
+ }
+
+ private static IActivityClientController getActivityClientController() {
+ final IActivityClientController controller = INTERFACE_SINGLETON.mKnownInstance;
+ return controller != null ? controller : INTERFACE_SINGLETON.get();
+ }
+
+ private static final Singleton<ActivityClient> sInstance = new Singleton<ActivityClient>() {
+ @Override
+ protected ActivityClient create() {
+ return new ActivityClient();
+ }
+ };
+
+ private static final ActivityClientControllerSingleton INTERFACE_SINGLETON =
+ new ActivityClientControllerSingleton();
+
+ private static class ActivityClientControllerSingleton
+ extends Singleton<IActivityClientController> {
+ /**
+ * A quick look up to reduce potential extra binder transactions. E.g. getting activity
+ * task manager from service manager and controller from activity task manager.
+ */
+ IActivityClientController mKnownInstance;
+
+ @Override
+ protected IActivityClientController create() {
+ try {
+ return ActivityTaskManager.getService().getActivityClientController();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/android-34/android/app/ActivityGroup.java b/android-34/android/app/ActivityGroup.java
new file mode 100644
index 0000000..cb06eea
--- /dev/null
+++ b/android-34/android/app/ActivityGroup.java
@@ -0,0 +1,132 @@
+/*
+ * 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.app;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.HashMap;
+
+/**
+ * A screen that contains and runs multiple embedded activities.
+ *
+ * @deprecated Use the new {@link Fragment} and {@link FragmentManager} APIs
+ * instead; these are also
+ * available on older platforms through the Android compatibility package.
+ */
+@Deprecated
+public class ActivityGroup extends Activity {
+ private static final String STATES_KEY = "android:states";
+ static final String PARENT_NON_CONFIG_INSTANCE_KEY = "android:parent_non_config_instance";
+
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ protected LocalActivityManager mLocalActivityManager;
+
+ public ActivityGroup() {
+ this(true);
+ }
+
+ public ActivityGroup(boolean singleActivityMode) {
+ mLocalActivityManager = new LocalActivityManager(this, singleActivityMode);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle states = savedInstanceState != null
+ ? (Bundle) savedInstanceState.getBundle(STATES_KEY) : null;
+ mLocalActivityManager.dispatchCreate(states);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLocalActivityManager.dispatchResume();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ Bundle state = mLocalActivityManager.saveInstanceState();
+ if (state != null) {
+ outState.putBundle(STATES_KEY, state);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mLocalActivityManager.dispatchPause(isFinishing());
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mLocalActivityManager.dispatchStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mLocalActivityManager.dispatchDestroy(isFinishing());
+ }
+
+ /**
+ * Returns a HashMap mapping from child activity ids to the return values
+ * from calls to their onRetainNonConfigurationInstance methods.
+ *
+ * {@hide}
+ */
+ @Override
+ public HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+ return mLocalActivityManager.dispatchRetainNonConfigurationInstance();
+ }
+
+ public Activity getCurrentActivity() {
+ return mLocalActivityManager.getCurrentActivity();
+ }
+
+ public final LocalActivityManager getLocalActivityManager() {
+ return mLocalActivityManager;
+ }
+
+ @Override
+ void dispatchActivityResult(String who, int requestCode, int resultCode,
+ Intent data, String reason) {
+ if (who != null) {
+ Activity act = mLocalActivityManager.getActivity(who);
+ /*
+ if (false) Log.v(
+ TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ + ", resCode=" + resultCode + ", data=" + data
+ + ", rec=" + rec);
+ */
+ if (act != null) {
+ act.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+ }
+ super.dispatchActivityResult(who, requestCode, resultCode, data, reason);
+ }
+}
+
+
diff --git a/android-34/android/app/ActivityManager.java b/android-34/android/app/ActivityManager.java
new file mode 100644
index 0000000..b5ee895
--- /dev/null
+++ b/android-34/android/app/ActivityManager.java
@@ -0,0 +1,5928 @@
+/*
+ * 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.app;
+
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+import android.Manifest;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+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.annotation.TestApi;
+import android.annotation.UserIdInt;
+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.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.hardware.HardwareBuffer;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.WorkSource;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.DisplayMetrics;
+import android.util.Singleton;
+import android.util.Size;
+import android.window.TaskSnapshot;
+
+import com.android.internal.app.LocalePicker;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.RoSystemProperties;
+import com.android.internal.os.TransferPipe;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.MemInfoReader;
+import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+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.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+/**
+ * <p>
+ * This class gives information about, and interacts
+ * with, activities, services, and the containing
+ * process.
+ * </p>
+ *
+ * <p>
+ * A number of the methods in this class are for
+ * debugging or informational purposes and they should
+ * not be used to affect any runtime behavior of
+ * your app. These methods are called out as such in
+ * the method level documentation.
+ * </p>
+ *
+ *<p>
+ * Most application developers should not have the need to
+ * use this class, most of whose methods are for specialized
+ * use cases. However, a few methods are more broadly applicable.
+ * For instance, {@link android.app.ActivityManager#isLowRamDevice() isLowRamDevice()}
+ * enables your app to detect whether it is running on a low-memory device,
+ * and behave accordingly.
+ * {@link android.app.ActivityManager#clearApplicationUserData() clearApplicationUserData()}
+ * is for apps with reset-data functionality.
+ * </p>
+ *
+ * <p>
+ * In some special use cases, where an app interacts with
+ * its Task stack, the app may use the
+ * {@link android.app.ActivityManager.AppTask} and
+ * {@link android.app.ActivityManager.RecentTaskInfo} inner
+ * classes. However, in general, the methods in this class should
+ * be used for testing and debugging purposes only.
+ * </p>
+ */
+@SystemService(Context.ACTIVITY_SERVICE)
+public class ActivityManager {
+ private static String TAG = "ActivityManager";
+
+ @UnsupportedAppUsage
+ private final Context mContext;
+
+ private static volatile boolean sSystemReady = false;
+
+
+ private static final int FIRST_START_FATAL_ERROR_CODE = -100;
+ private static final int LAST_START_FATAL_ERROR_CODE = -1;
+ private static final int FIRST_START_SUCCESS_CODE = 0;
+ private static final int LAST_START_SUCCESS_CODE = 99;
+ private static final int FIRST_START_NON_FATAL_ERROR_CODE = 100;
+ private static final int LAST_START_NON_FATAL_ERROR_CODE = 199;
+
+ /**
+ * Disable hidden API checks for the newly started instrumentation.
+ * @hide
+ */
+ public static final int INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS = 1 << 0;
+ /**
+ * Grant full access to the external storage for the newly started instrumentation.
+ * @hide
+ */
+ public static final int INSTR_FLAG_DISABLE_ISOLATED_STORAGE = 1 << 1;
+
+ /**
+ * Disable test API access for the newly started instrumentation.
+ * @hide
+ */
+ public static final int INSTR_FLAG_DISABLE_TEST_API_CHECKS = 1 << 2;
+
+ /**
+ * Do not restart the target process when starting or finishing instrumentation.
+ * @hide
+ */
+ public static final int INSTR_FLAG_NO_RESTART = 1 << 3;
+ /**
+ * Force the check that instrumentation and the target package are signed with the same
+ * certificate even if {@link Build#IS_DEBUGGABLE} is {@code true}.
+ * @hide
+ */
+ public static final int INSTR_FLAG_ALWAYS_CHECK_SIGNATURE = 1 << 4;
+ /**
+ * Instrument Sdk Sandbox process that corresponds to the target package.
+ * @hide
+ */
+ public static final int INSTR_FLAG_INSTRUMENT_SDK_SANDBOX = 1 << 5;
+
+ static final class MyUidObserver extends UidObserver {
+ final OnUidImportanceListener mListener;
+ final Context mContext;
+
+ MyUidObserver(OnUidImportanceListener listener, Context clientContext) {
+ mListener = listener;
+ mContext = clientContext;
+ }
+
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ mListener.onUidImportance(uid, RunningAppProcessInfo.procStateToImportanceForClient(
+ procState, mContext));
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ mListener.onUidImportance(uid, RunningAppProcessInfo.IMPORTANCE_GONE);
+ }
+ }
+
+ final ArrayMap<OnUidImportanceListener, MyUidObserver> mImportanceListeners = new ArrayMap<>();
+
+ /**
+ * Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
+ * Will be called when a Uid has become frozen or unfrozen.
+ */
+ private final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks =
+ new ArrayMap<>();
+
+ private final IUidFrozenStateChangedCallback mFrozenStateChangedCallback =
+ new IUidFrozenStateChangedCallback.Stub() {
+ @Override
+ public void onUidFrozenStateChanged(int[] uids, int[] frozenStates) {
+ synchronized (mFrozenStateChangedCallbacks) {
+ mFrozenStateChangedCallbacks.forEach((callback, executor) -> {
+ executor.execute(
+ () -> callback.onUidFrozenStateChanged(uids, frozenStates));
+ });
+ }
+ }
+ };
+
+ /**
+ * Callback object for {@link #registerUidFrozenStateChangedCallback}
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public interface UidFrozenStateChangedCallback {
+ /**
+ * Indicates that the UID was frozen.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ int UID_FROZEN_STATE_FROZEN = 1;
+
+ /**
+ * Indicates that the UID was unfrozen.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ int UID_FROZEN_STATE_UNFROZEN = 2;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = {"UID_FROZEN_STATE_"}, value = {
+ UID_FROZEN_STATE_FROZEN,
+ UID_FROZEN_STATE_UNFROZEN,
+ })
+ public @interface UidFrozenState {}
+
+ /**
+ * Notify the client that the frozen states of an array of UIDs have changed.
+ *
+ * @param uids The UIDs for which the frozen state has changed
+ * @param frozenStates Frozen state for each UID index, Will be set to
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
+ * when the UID is frozen. When the UID is unfrozen,
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN}
+ * will be set.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ void onUidFrozenStateChanged(@NonNull int[] uids,
+ @NonNull @UidFrozenState int[] frozenStates);
+ }
+
+ /**
+ * Register a {@link UidFrozenStateChangedCallback} object to receive notification
+ * when a UID is frozen or unfrozen. Will throw an exception if the same
+ * callback object is registered more than once.
+ *
+ * @param executor The executor that the callback will be run from.
+ * @param callback The callback to be registered. Callbacks for previous frozen/unfrozen
+ * UID changes will not be delivered. Only changes in state from the point of
+ * registration onward will be reported.
+ * @throws IllegalStateException if the {@code callback} is already registered.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public void registerUidFrozenStateChangedCallback(
+ @NonNull Executor executor,
+ @NonNull UidFrozenStateChangedCallback callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ synchronized (mFrozenStateChangedCallbacks) {
+ if (mFrozenStateChangedCallbacks.containsKey(callback)) {
+ throw new IllegalStateException("Callback already registered: " + callback);
+ }
+ mFrozenStateChangedCallbacks.put(callback, executor);
+ if (mFrozenStateChangedCallbacks.size() > 1) {
+ /* There's no need to register more than one binder interface */
+ return;
+ }
+
+ try {
+ getService().registerUidFrozenStateChangedCallback(mFrozenStateChangedCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregister a {@link UidFrozenStateChangedCallback} callback.
+ * @param callback The callback to be unregistered.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public void unregisterUidFrozenStateChangedCallback(
+ @NonNull UidFrozenStateChangedCallback callback) {
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ synchronized (mFrozenStateChangedCallbacks) {
+ mFrozenStateChangedCallbacks.remove(callback);
+ if (mFrozenStateChangedCallbacks.isEmpty()) {
+ try {
+ getService().unregisterUidFrozenStateChangedCallback(
+ mFrozenStateChangedCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * Query the frozen state of a list of UIDs.
+ *
+ * @param uids the array of UIDs which the client would like to know the frozen state of.
+ * @return An array containing the frozen state for each requested UID, by index. Will be set
+ * to {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
+ * if the UID is frozen. If the UID is not frozen or not found,
+ * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN}
+ * will be set.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public @NonNull @UidFrozenStateChangedCallback.UidFrozenState
+ int[] getUidFrozenState(@NonNull int[] uids) {
+ try {
+ return getService().getUidFrozenState(uids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
+ * <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
+ * uninstalled in lieu of the declaring one. The package named here must be
+ * signed with the same certificate as the one declaring the {@code <meta-data>}.
+ */
+ public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
+
+ // NOTE: Before adding a new start result, please reference the defined ranges to ensure the
+ // result is properly categorized.
+
+ /**
+ * Result for IActivityManager.startVoiceActivity: active session is currently hidden.
+ * @hide
+ */
+ public static final int START_VOICE_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE;
+
+ /**
+ * Result for IActivityManager.startVoiceActivity: active session does not match
+ * the requesting token.
+ * @hide
+ */
+ public static final int START_VOICE_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 1;
+
+ /**
+ * Result for IActivityManager.startActivity: trying to start a background user
+ * activity that shouldn't be displayed for all users.
+ * @hide
+ */
+ public static final int START_NOT_CURRENT_USER_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 2;
+
+ /**
+ * Result for IActivityManager.startActivity: trying to start an activity under voice
+ * control when that activity does not support the VOICE category.
+ * @hide
+ */
+ public static final int START_NOT_VOICE_COMPATIBLE = FIRST_START_FATAL_ERROR_CODE + 3;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * start had to be canceled.
+ * @hide
+ */
+ public static final int START_CANCELED = FIRST_START_FATAL_ERROR_CODE + 4;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * thing being started is not an activity.
+ * @hide
+ */
+ public static final int START_NOT_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 5;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * caller does not have permission to start the activity.
+ * @hide
+ */
+ public static final int START_PERMISSION_DENIED = FIRST_START_FATAL_ERROR_CODE + 6;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * caller has requested both to forward a result and to receive
+ * a result.
+ * @hide
+ */
+ public static final int START_FORWARD_AND_REQUEST_CONFLICT = FIRST_START_FATAL_ERROR_CODE + 7;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * requested class is not found.
+ * @hide
+ */
+ public static final int START_CLASS_NOT_FOUND = FIRST_START_FATAL_ERROR_CODE + 8;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * given Intent could not be resolved to an activity.
+ * @hide
+ */
+ public static final int START_INTENT_NOT_RESOLVED = FIRST_START_FATAL_ERROR_CODE + 9;
+
+ /**
+ * Result for IActivityManager.startAssistantActivity: active session is currently hidden.
+ * @hide
+ */
+ public static final int START_ASSISTANT_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE + 10;
+
+ /**
+ * Result for IActivityManager.startAssistantActivity: active session does not match
+ * the requesting token.
+ * @hide
+ */
+ public static final int START_ASSISTANT_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 11;
+
+ /**
+ * Result for IActivityManaqer.startActivity: the activity was started
+ * successfully as normal.
+ * @hide
+ */
+ public static final int START_SUCCESS = FIRST_START_SUCCESS_CODE;
+
+ /**
+ * Result for IActivityManaqer.startActivity: the caller asked that the Intent not
+ * be executed if it is the recipient, and that is indeed the case.
+ * @hide
+ */
+ public static final int START_RETURN_INTENT_TO_CALLER = FIRST_START_SUCCESS_CODE + 1;
+
+ /**
+ * Result for IActivityManaqer.startActivity: activity was started or brought forward in an
+ * existing task which was brought to the foreground.
+ * @hide
+ */
+ public static final int START_TASK_TO_FRONT = FIRST_START_SUCCESS_CODE + 2;
+
+ /**
+ * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+ * the given Intent was given to the existing top activity.
+ * @hide
+ */
+ public static final int START_DELIVERED_TO_TOP = FIRST_START_SUCCESS_CODE + 3;
+
+ /**
+ * Result for IActivityManaqer.startActivity: request was canceled because
+ * app switches are temporarily canceled to ensure the user's last request
+ * (such as pressing home) is performed.
+ * @hide
+ */
+ public static final int START_SWITCHES_CANCELED = FIRST_START_NON_FATAL_ERROR_CODE;
+
+ /**
+ * Result for IActivityManaqer.startActivity: a new activity was attempted to be started
+ * while in Lock Task Mode.
+ * @hide
+ */
+ public static final int START_RETURN_LOCK_TASK_MODE_VIOLATION =
+ FIRST_START_NON_FATAL_ERROR_CODE + 1;
+
+ /**
+ * Result for IActivityManaqer.startActivity: a new activity start was aborted. Never returned
+ * externally.
+ * @hide
+ */
+ public static final int START_ABORTED = FIRST_START_NON_FATAL_ERROR_CODE + 2;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: do special start mode where
+ * a new activity is launched only if it is needed.
+ * @hide
+ */
+ public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: launch the app for
+ * debugging.
+ * @hide
+ */
+ public static final int START_FLAG_DEBUG = 1<<1;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: launch the app for
+ * allocation tracking.
+ * @hide
+ */
+ public static final int START_FLAG_TRACK_ALLOCATION = 1<<2;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: launch the app with
+ * native debugging support.
+ * @hide
+ */
+ public static final int START_FLAG_NATIVE_DEBUGGING = 1<<3;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: launch the app for
+ * debugging and suspend threads.
+ * @hide
+ */
+ public static final int START_FLAG_DEBUG_SUSPEND = 1 << 4;
+
+ /**
+ * Result for IActivityManaqer.broadcastIntent: success!
+ * @hide
+ */
+ public static final int BROADCAST_SUCCESS = 0;
+
+ /**
+ * Result for IActivityManaqer.broadcastIntent: attempt to broadcast
+ * a sticky intent without appropriate permission.
+ * @hide
+ */
+ public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
+
+ /**
+ * Result for IActivityManager.broadcastIntent: trying to send a broadcast
+ * to a stopped user. Fail.
+ * @hide
+ */
+ public static final int BROADCAST_FAILED_USER_STOPPED = -2;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent type is unknown.
+ * @hide
+ */
+ public static final int INTENT_SENDER_UNKNOWN = 0;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a sendBroadcast operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_BROADCAST = 1;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a startActivity operation.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int INTENT_SENDER_ACTIVITY = 2;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for an activity result operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a startService operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_SERVICE = 4;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a startForegroundService operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
+
+ /** @hide User operation call: success! */
+ public static final int USER_OP_SUCCESS = 0;
+
+ /** @hide User operation call: given user id is not known. */
+ public static final int USER_OP_UNKNOWN_USER = -1;
+
+ /** @hide User operation call: given user id is the current user, can't be stopped. */
+ public static final int USER_OP_IS_CURRENT = -2;
+
+ /** @hide User operation call: system user can't be stopped. */
+ public static final int USER_OP_ERROR_IS_SYSTEM = -3;
+
+ /** @hide User operation call: one of related users cannot be stopped. */
+ public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
+
+ /**
+ * Process states, describing the kind of state a particular process is in.
+ * When updating these, make sure to also check all related references to the
+ * constant in code, and update these arrays:
+ *
+ * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
+ * @see com.android.server.am.ProcessList#sProcStateToProcMem
+ * @see com.android.server.am.ProcessList#sFirstAwakePssTimes
+ * @see com.android.server.am.ProcessList#sSameAwakePssTimes
+ * @see com.android.server.am.ProcessList#sTestFirstPssTimes
+ * @see com.android.server.am.ProcessList#sTestSamePssTimes
+ * @hide
+ */
+ @IntDef(flag = false, prefix = { "PROCESS_STATE_" }, value = {
+ PROCESS_STATE_UNKNOWN, // -1
+ PROCESS_STATE_PERSISTENT, // 0
+ PROCESS_STATE_PERSISTENT_UI,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_BOUND_TOP,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ PROCESS_STATE_IMPORTANT_FOREGROUND,
+ PROCESS_STATE_IMPORTANT_BACKGROUND,
+ PROCESS_STATE_TRANSIENT_BACKGROUND,
+ PROCESS_STATE_BACKUP,
+ PROCESS_STATE_SERVICE,
+ PROCESS_STATE_RECEIVER,
+ PROCESS_STATE_TOP_SLEEPING,
+ PROCESS_STATE_HEAVY_WEIGHT,
+ PROCESS_STATE_HOME,
+ PROCESS_STATE_LAST_ACTIVITY,
+ PROCESS_STATE_CACHED_ACTIVITY,
+ PROCESS_STATE_CACHED_ACTIVITY_CLIENT,
+ PROCESS_STATE_CACHED_RECENT,
+ PROCESS_STATE_CACHED_EMPTY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProcessState {}
+
+ /*
+ * PROCESS_STATE_* must come from frameworks/base/core/java/android/app/ProcessStateEnum.aidl.
+ * This is to make sure that Java side uses the same values as native.
+ */
+
+ /** @hide Not a real process state. */
+ public static final int PROCESS_STATE_UNKNOWN = ProcessStateEnum.UNKNOWN;
+
+ /** @hide Process is a persistent system process. */
+ public static final int PROCESS_STATE_PERSISTENT = ProcessStateEnum.PERSISTENT;
+
+ /** @hide Process is a persistent system process and is doing UI. */
+ public static final int PROCESS_STATE_PERSISTENT_UI = ProcessStateEnum.PERSISTENT_UI;
+
+ /** @hide Process is hosting the current top activities. Note that this covers
+ * all activities that are visible to the user. */
+ @UnsupportedAppUsage
+ @TestApi
+ public static final int PROCESS_STATE_TOP = ProcessStateEnum.TOP;
+
+ /** @hide Process is bound to a TOP app. */
+ public static final int PROCESS_STATE_BOUND_TOP = ProcessStateEnum.BOUND_TOP;
+
+ /** @hide Process is hosting a foreground service. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = ProcessStateEnum.FOREGROUND_SERVICE;
+
+ /** @hide Process is hosting a foreground service due to a system binding. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE =
+ ProcessStateEnum.BOUND_FOREGROUND_SERVICE;
+
+ /** @hide Process is important to the user, and something they are aware of. */
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND =
+ ProcessStateEnum.IMPORTANT_FOREGROUND;
+
+ /** @hide Process is important to the user, but not something they are aware of. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND =
+ ProcessStateEnum.IMPORTANT_BACKGROUND;
+
+ /** @hide Process is in the background transient so we will try to keep running. */
+ public static final int PROCESS_STATE_TRANSIENT_BACKGROUND =
+ ProcessStateEnum.TRANSIENT_BACKGROUND;
+
+ /** @hide Process is in the background running a backup/restore operation. */
+ public static final int PROCESS_STATE_BACKUP = ProcessStateEnum.BACKUP;
+
+ /** @hide Process is in the background running a service. Unlike oom_adj, this level
+ * is used for both the normal running in background state and the executing
+ * operations state. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROCESS_STATE_SERVICE = ProcessStateEnum.SERVICE;
+
+ /** @hide Process is in the background running a receiver. Note that from the
+ * perspective of oom_adj, receivers run at a higher foreground level, but for our
+ * prioritization here that is not necessary and putting them below services means
+ * many fewer changes in some process states as they receive broadcasts. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROCESS_STATE_RECEIVER = ProcessStateEnum.RECEIVER;
+
+ /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+ public static final int PROCESS_STATE_TOP_SLEEPING = ProcessStateEnum.TOP_SLEEPING;
+
+ /** @hide Process is in the background, but it can't restore its state so we want
+ * to try to avoid killing it. */
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = ProcessStateEnum.HEAVY_WEIGHT;
+
+ /** @hide Process is in the background but hosts the home activity. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROCESS_STATE_HOME = ProcessStateEnum.HOME;
+
+ /** @hide Process is in the background but hosts the last shown activity. */
+ public static final int PROCESS_STATE_LAST_ACTIVITY = ProcessStateEnum.LAST_ACTIVITY;
+
+ /** @hide Process is being cached for later use and contains activities. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int PROCESS_STATE_CACHED_ACTIVITY = ProcessStateEnum.CACHED_ACTIVITY;
+
+ /** @hide Process is being cached for later use and is a client of another cached
+ * process that contains activities. */
+ public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT =
+ ProcessStateEnum.CACHED_ACTIVITY_CLIENT;
+
+ /** @hide Process is being cached for later use and has an activity that corresponds
+ * to an existing recent task. */
+ public static final int PROCESS_STATE_CACHED_RECENT = ProcessStateEnum.CACHED_RECENT;
+
+ /** @hide Process is being cached for later use and is empty. */
+ public static final int PROCESS_STATE_CACHED_EMPTY = ProcessStateEnum.CACHED_EMPTY;
+
+ /** @hide Process does not exist. */
+ public static final int PROCESS_STATE_NONEXISTENT = ProcessStateEnum.NONEXISTENT;
+
+ /**
+ * The set of flags for process capability.
+ * Keep it in sync with ProcessCapability in atoms.proto.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "PROCESS_CAPABILITY_" }, value = {
+ PROCESS_CAPABILITY_NONE,
+ PROCESS_CAPABILITY_FOREGROUND_LOCATION,
+ PROCESS_CAPABILITY_FOREGROUND_CAMERA,
+ PROCESS_CAPABILITY_FOREGROUND_MICROPHONE,
+ PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK,
+ PROCESS_CAPABILITY_BFSL,
+ PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProcessCapability {}
+
+ /**
+ * Used to log FGS API events from CAMERA API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_CAMERA = 1;
+
+ /**
+ * Used to log FGS API events from BLUETOOTH API, used
+ * with FGS type of CONNECTED_DEVICE
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_BLUETOOTH = 2;
+ /**
+ * Used to log FGS API events from Location API.
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_LOCATION = 3;
+ /**
+ * Used to log FGS API events from media playback API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK = 4;
+ /**
+ * Used to log FGS API events from Audio API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_AUDIO = 5;
+ /**
+ * Used to log FGS API events from microphone API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_MICROPHONE = 6;
+ /**
+ * Used to log FGS API events from phone API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_PHONE_CALL = 7;
+ /**
+ * Used to log FGS API events from USB API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_USB = 8;
+ /**
+ * Used to log FGS API events from CDM API
+ * @hide
+ */
+ @SystemApi
+ public static final int FOREGROUND_SERVICE_API_TYPE_CDM = 9;
+
+ /**
+ * Constants used to denote what API type
+ * is creating an API event for logging.
+ * @hide
+ */
+ @IntDef(flag = false, prefix = { "FOREGROUND_SERVICE_API_TYPE_" }, value = {
+ FOREGROUND_SERVICE_API_TYPE_CAMERA,
+ FOREGROUND_SERVICE_API_TYPE_BLUETOOTH,
+ FOREGROUND_SERVICE_API_TYPE_LOCATION,
+ FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ FOREGROUND_SERVICE_API_TYPE_AUDIO,
+ FOREGROUND_SERVICE_API_TYPE_MICROPHONE,
+ FOREGROUND_SERVICE_API_TYPE_PHONE_CALL,
+ FOREGROUND_SERVICE_API_TYPE_USB,
+ FOREGROUND_SERVICE_API_TYPE_CDM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForegroundServiceApiType {}
+
+ /**
+ * Used to log a start event for an FGS API
+ * @hide
+ */
+ public static final int FOREGROUND_SERVICE_API_EVENT_BEGIN = 1;
+ /**
+ * Used to log a stop event for an FGS API
+ * @hide
+ */
+ public static final int FOREGROUND_SERVICE_API_EVENT_END = 2;
+ /**
+ * Constants used to denote API state
+ * during an API event for logging.
+ * @hide
+ */
+ @IntDef(flag = false, prefix = { "FOREGROUND_SERVICE_API_EVENT_" }, value = {
+ FOREGROUND_SERVICE_API_EVENT_BEGIN,
+ FOREGROUND_SERVICE_API_EVENT_END,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForegroundServiceApiEvent {}
+
+ /** @hide Process does not have any capability */
+ @SystemApi
+ public static final int PROCESS_CAPABILITY_NONE = 0;
+
+ /** @hide Process can access location while in foreground */
+ @SystemApi
+ public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0;
+
+ /** @hide Process can access camera while in foreground */
+ @SystemApi
+ public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1;
+
+ /** @hide Process can access microphone while in foreground */
+ @SystemApi
+ public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2;
+
+ /** @hide Process can access network despite any power saving restrictions */
+ @TestApi
+ public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 1 << 3;
+
+ /**
+ * Flag used to indicate whether an app is allowed to start a foreground service from the
+ * background, decided by the procstates. ("BFSL" == "background foreground service launch")
+ *
+ * - BFSL has a number of exemptions -- e.g. when an app is power-allowlisted, including
+ * temp-allowlist -- but this capability is *not* used to represent such exemptions.
+ * This is set only based on the procstate and the foreground service type.
+ * - Basically, procstates <= BFGS (i.e. BFGS, FGS, BTOP, TOP, ...) are BFSL-allowed,
+ * and that's how things worked on Android S/T.
+ * However, Android U added a "SHORT_SERVICE" FGS type, which gets the FGS procstate
+ * *but* can't start another FGS. So now we use this flag to decide whether FGS/BFGS
+ * procstates are BFSL-allowed. (higher procstates, such as BTOP, will still always be
+ * BFSL-allowed.)
+ * We propagate this flag across via service bindings and provider references.
+ *
+ * @hide
+ */
+ public static final int PROCESS_CAPABILITY_BFSL = 1 << 4;
+
+ /**
+ * @hide
+ * Process can access network at a high enough proc state despite any user restrictions.
+ */
+ @TestApi
+ public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 1 << 5;
+
+ /**
+ * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
+ *
+ * Don't expose it as TestApi -- we may add new capabilities any time, which could
+ * break CTS tests if they relied on it.
+ */
+ public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
+ | PROCESS_CAPABILITY_FOREGROUND_CAMERA
+ | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+ | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
+ | PROCESS_CAPABILITY_BFSL
+ | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+
+ /**
+ * All implicit capabilities. There are capabilities that process automatically have.
+ * @hide
+ */
+ @TestApi
+ public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = PROCESS_CAPABILITY_FOREGROUND_CAMERA
+ | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+
+ /**
+ * Print capability bits in human-readable form.
+ * @hide
+ */
+ public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
+ pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
+ }
+
+ /** @hide */
+ public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
+ sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
+ }
+
+ /**
+ * Print capability bits in human-readable form.
+ * @hide
+ */
+ public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
+ printCapabilitiesSummary(pw, caps);
+ final int remain = caps & ~PROCESS_CAPABILITY_ALL;
+ if (remain != 0) {
+ pw.print("+0x");
+ pw.print(Integer.toHexString(remain));
+ }
+ }
+
+ /** @hide */
+ public static String getCapabilitiesSummary(@ProcessCapability int caps) {
+ final StringBuilder sb = new StringBuilder();
+ printCapabilitiesSummary(sb, caps);
+ return sb.toString();
+ }
+
+ // NOTE: If PROCESS_STATEs are added, then new fields must be added
+ // to frameworks/base/core/proto/android/app/enums.proto and the following method must
+ // be updated to correctly map between them.
+ // However, if the current ActivityManager values are merely modified, no update should be made
+ // to enums.proto, to which values can only be added but never modified. Note that the proto
+ // versions do NOT have the ordering restrictions of the ActivityManager process state.
+ /**
+ * Maps ActivityManager.PROCESS_STATE_ values to enums.proto ProcessStateEnum value.
+ *
+ * @param amInt a process state of the form ActivityManager.PROCESS_STATE_
+ * @return the value of the corresponding enums.proto ProcessStateEnum value.
+ * @hide
+ */
+ public static final int processStateAmToProto(int amInt) {
+ switch (amInt) {
+ case PROCESS_STATE_UNKNOWN:
+ return AppProtoEnums.PROCESS_STATE_UNKNOWN;
+ case PROCESS_STATE_PERSISTENT:
+ return AppProtoEnums.PROCESS_STATE_PERSISTENT;
+ case PROCESS_STATE_PERSISTENT_UI:
+ return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI;
+ case PROCESS_STATE_TOP:
+ return AppProtoEnums.PROCESS_STATE_TOP;
+ case PROCESS_STATE_BOUND_TOP:
+ return AppProtoEnums.PROCESS_STATE_BOUND_TOP;
+ case PROCESS_STATE_FOREGROUND_SERVICE:
+ return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE;
+ case PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ return AppProtoEnums.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ case PROCESS_STATE_IMPORTANT_FOREGROUND:
+ return AppProtoEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ case PROCESS_STATE_IMPORTANT_BACKGROUND:
+ return AppProtoEnums.PROCESS_STATE_IMPORTANT_BACKGROUND;
+ case PROCESS_STATE_TRANSIENT_BACKGROUND:
+ return AppProtoEnums.PROCESS_STATE_TRANSIENT_BACKGROUND;
+ case PROCESS_STATE_BACKUP:
+ return AppProtoEnums.PROCESS_STATE_BACKUP;
+ case PROCESS_STATE_SERVICE:
+ return AppProtoEnums.PROCESS_STATE_SERVICE;
+ case PROCESS_STATE_RECEIVER:
+ return AppProtoEnums.PROCESS_STATE_RECEIVER;
+ case PROCESS_STATE_TOP_SLEEPING:
+ return AppProtoEnums.PROCESS_STATE_TOP_SLEEPING;
+ case PROCESS_STATE_HEAVY_WEIGHT:
+ return AppProtoEnums.PROCESS_STATE_HEAVY_WEIGHT;
+ case PROCESS_STATE_HOME:
+ return AppProtoEnums.PROCESS_STATE_HOME;
+ case PROCESS_STATE_LAST_ACTIVITY:
+ return AppProtoEnums.PROCESS_STATE_LAST_ACTIVITY;
+ case PROCESS_STATE_CACHED_ACTIVITY:
+ return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY;
+ case PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+ case PROCESS_STATE_CACHED_RECENT:
+ return AppProtoEnums.PROCESS_STATE_CACHED_RECENT;
+ case PROCESS_STATE_CACHED_EMPTY:
+ return AppProtoEnums.PROCESS_STATE_CACHED_EMPTY;
+ case PROCESS_STATE_NONEXISTENT:
+ return AppProtoEnums.PROCESS_STATE_NONEXISTENT;
+ default:
+ // ActivityManager process state (amInt)
+ // could not be mapped to an AppProtoEnums ProcessState state.
+ return AppProtoEnums.PROCESS_STATE_UNKNOWN_TO_PROTO;
+ }
+ }
+
+ /** @hide The lowest process state number */
+ public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;
+
+ /** @hide The highest process state number */
+ public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
+
+ /** @hide Should this process state be considered a background state? */
+ public static final boolean isProcStateBackground(int procState) {
+ return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
+ }
+
+ /** @hide Should this process state be considered in the cache? */
+ public static final boolean isProcStateCached(int procState) {
+ return procState >= PROCESS_STATE_CACHED_ACTIVITY;
+ }
+
+ /** @hide Is this a foreground service type? */
+ public static boolean isForegroundService(int procState) {
+ return procState == PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
+ /** @hide requestType for assist context: only basic information. */
+ public static final int ASSIST_CONTEXT_BASIC = 0;
+
+ /** @hide requestType for assist context: generate full AssistStructure. */
+ public static final int ASSIST_CONTEXT_FULL = 1;
+
+ /** @hide requestType for assist context: generate full AssistStructure for autofill. */
+ public static final int ASSIST_CONTEXT_AUTOFILL = 2;
+
+ /** @hide requestType for assist context: generate AssistContent but not AssistStructure. */
+ public static final int ASSIST_CONTEXT_CONTENT = 3;
+
+ /** @hide Flag for registerUidObserver: report changes in process state. */
+ public static final int UID_OBSERVER_PROCSTATE = 1<<0;
+
+ /** @hide Flag for registerUidObserver: report uid gone. */
+ public static final int UID_OBSERVER_GONE = 1<<1;
+
+ /** @hide Flag for registerUidObserver: report uid has become idle. */
+ public static final int UID_OBSERVER_IDLE = 1<<2;
+
+ /** @hide Flag for registerUidObserver: report uid has become active. */
+ public static final int UID_OBSERVER_ACTIVE = 1<<3;
+
+ /** @hide Flag for registerUidObserver: report uid cached state has changed. */
+ public static final int UID_OBSERVER_CACHED = 1<<4;
+
+ /** @hide Flag for registerUidObserver: report uid capability has changed. */
+ public static final int UID_OBSERVER_CAPABILITY = 1<<5;
+
+ /** @hide Flag for registerUidObserver: report pid oom adj has changed. */
+ public static final int UID_OBSERVER_PROC_OOM_ADJ = 1 << 6;
+
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: normal free-to-run operation. */
+ public static final int APP_START_MODE_NORMAL = 0;
+
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later. */
+ public static final int APP_START_MODE_DELAYED = 1;
+
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later, with
+ * rigid errors (throwing exception). */
+ public static final int APP_START_MODE_DELAYED_RIGID = 2;
+
+ /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: disable/cancel pending
+ * launches; this is the mode for ephemeral apps. */
+ public static final int APP_START_MODE_DISABLED = 3;
+
+ /**
+ * Lock task mode is not active.
+ */
+ public static final int LOCK_TASK_MODE_NONE = 0;
+
+ /**
+ * Full lock task mode is active.
+ */
+ public static final int LOCK_TASK_MODE_LOCKED = 1;
+
+ /**
+ * App pinning mode is active.
+ */
+ public static final int LOCK_TASK_MODE_PINNED = 2;
+
+ Point mAppTaskThumbnailSize;
+
+ @UnsupportedAppUsage
+ /*package*/ ActivityManager(Context context, Handler handler) {
+ mContext = context;
+ }
+
+ /**
+ * Returns whether the launch was successful.
+ * @hide
+ */
+ public static final boolean isStartResultSuccessful(int result) {
+ return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
+ }
+
+ /**
+ * Returns whether the launch result was a fatal error.
+ * @hide
+ */
+ public static final boolean isStartResultFatalError(int result) {
+ return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
+ }
+
+ /**
+ * Screen compatibility mode: the application most always run in
+ * compatibility mode.
+ * @hide
+ */
+ public static final int COMPAT_MODE_ALWAYS = -1;
+
+ /**
+ * Screen compatibility mode: the application can never run in
+ * compatibility mode.
+ * @hide
+ */
+ public static final int COMPAT_MODE_NEVER = -2;
+
+ /**
+ * Screen compatibility mode: unknown.
+ * @hide
+ */
+ public static final int COMPAT_MODE_UNKNOWN = -3;
+
+ /**
+ * Screen compatibility mode: the application currently has compatibility
+ * mode disabled.
+ * @hide
+ */
+ public static final int COMPAT_MODE_DISABLED = 0;
+
+ /**
+ * Screen compatibility mode: the application currently has compatibility
+ * mode enabled.
+ * @hide
+ */
+ public static final int COMPAT_MODE_ENABLED = 1;
+
+ /**
+ * Screen compatibility mode: request to toggle the application's
+ * compatibility mode.
+ * @hide
+ */
+ public static final int COMPAT_MODE_TOGGLE = 2;
+
+ private static final boolean DEVELOPMENT_FORCE_LOW_RAM =
+ SystemProperties.getBoolean("debug.force_low_ram", false);
+
+ /**
+ * Intent {@link Intent#ACTION_CLOSE_SYSTEM_DIALOGS} is too powerful to be unrestricted. We
+ * restrict its usage for a few legitimate use-cases only, regardless of targetSdk. For the
+ * other use-cases we drop the intent with a log message.
+ *
+ * Note that this is the lighter version of {@link ActivityManager
+ * #LOCK_DOWN_CLOSE_SYSTEM_DIALOGS} which is not gated on targetSdk in order to eliminate the
+ * abuse vector.
+ *
+ * @hide
+ */
+ @ChangeId
+ public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L;
+
+ /**
+ * Intent {@link Intent#ACTION_CLOSE_SYSTEM_DIALOGS} is too powerful to be unrestricted. So,
+ * apps targeting {@link Build.VERSION_CODES#S} or higher will crash if they try to send such
+ * intent and don't have permission {@code android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS}.
+ *
+ * Note that this is the more restrict version of {@link ActivityManager
+ * #DROP_CLOSE_SYSTEM_DIALOGS} that expects the app to stop sending aforementioned intent once
+ * it bumps its targetSdk to {@link Build.VERSION_CODES#S} or higher.
+ *
+ * @hide
+ */
+ @TestApi
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.S)
+ public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L;
+
+ // The background process restriction levels. The definitions here are meant for internal
+ // bookkeeping only.
+
+ /**
+ * Not a valid restriction level.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_UNKNOWN = 0;
+
+ /**
+ * No background restrictions at all, this should NEVER be used
+ * for any process other than selected system processes, currently it's reserved.
+ *
+ * <p>In the future, apps in {@link #RESTRICTION_LEVEL_EXEMPTED} would receive permissive
+ * background restrictions to protect the system from buggy behaviors; in other words,
+ * the {@link #RESTRICTION_LEVEL_EXEMPTED} would not be the truly "unrestricted" state, while
+ * the {@link #RESTRICTION_LEVEL_UNRESTRICTED} here would be the last resort if there is
+ * a strong reason to grant such a capability to a system app. </p>
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_UNRESTRICTED = 10;
+
+ /**
+ * The default background restriction level for the "unrestricted" apps set by the user,
+ * where it'll have the {@link android.app.AppOpsManager#OP_RUN_ANY_IN_BACKGROUND} set to
+ * ALLOWED, being added into the device idle allow list; however there will be still certain
+ * restrictions to apps in this level.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_EXEMPTED = 20;
+
+ /**
+ * The default background restriction level for all other apps, they'll be moved between
+ * various standby buckets, including
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_ACTIVE},
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET},
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_FREQUENT},
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE}.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_ADAPTIVE_BUCKET = 30;
+
+ /**
+ * The background restriction level where the apps will be placed in the restricted bucket
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_RESTRICTED_BUCKET = 40;
+
+ /**
+ * The background restricted level, where apps would get more restrictions,
+ * such as not allowed to launch foreground services besides on TOP.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
+
+ /**
+ * The most restricted level where the apps are considered "in-hibernation",
+ * its package visibility to the rest of the system is limited.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+
+ /**
+ * Not a valid restriction level, it defines the maximum numerical value of restriction level.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_MAX = 100;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_LEVEL_" }, value = {
+ RESTRICTION_LEVEL_UNKNOWN,
+ RESTRICTION_LEVEL_UNRESTRICTED,
+ RESTRICTION_LEVEL_EXEMPTED,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+ RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+ RESTRICTION_LEVEL_HIBERNATION,
+ RESTRICTION_LEVEL_MAX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionLevel{}
+
+ /** @hide */
+ public static String restrictionLevelToName(@RestrictionLevel int level) {
+ switch (level) {
+ case RESTRICTION_LEVEL_UNKNOWN:
+ return "unknown";
+ case RESTRICTION_LEVEL_UNRESTRICTED:
+ return "unrestricted";
+ case RESTRICTION_LEVEL_EXEMPTED:
+ return "exempted";
+ case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
+ return "adaptive_bucket";
+ case RESTRICTION_LEVEL_RESTRICTED_BUCKET:
+ return "restricted_bucket";
+ case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
+ return "background_restricted";
+ case RESTRICTION_LEVEL_HIBERNATION:
+ return "hibernation";
+ case RESTRICTION_LEVEL_MAX:
+ return "max";
+ default:
+ return "";
+ }
+ }
+
+ /** @hide */
+ public int getFrontActivityScreenCompatMode() {
+ try {
+ return getTaskService().getFrontActivityScreenCompatMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void setFrontActivityScreenCompatMode(int mode) {
+ try {
+ getTaskService().setFrontActivityScreenCompatMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public int getPackageScreenCompatMode(String packageName) {
+ try {
+ return getTaskService().getPackageScreenCompatMode(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void setPackageScreenCompatMode(String packageName, int mode) {
+ try {
+ getTaskService().setPackageScreenCompatMode(packageName, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public boolean getPackageAskScreenCompat(String packageName) {
+ try {
+ return getTaskService().getPackageAskScreenCompat(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void setPackageAskScreenCompat(String packageName, boolean ask) {
+ try {
+ getTaskService().setPackageAskScreenCompat(packageName, ask);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the approximate per-application memory class of the current
+ * device. This gives you an idea of how hard a memory limit you should
+ * impose on your application to let the overall system work best. The
+ * returned value is in megabytes; the baseline Android memory class is
+ * 16 (which happens to be the Java heap limit of those devices); some
+ * devices with more memory may return 24 or even higher numbers.
+ */
+ public int getMemoryClass() {
+ return staticGetMemoryClass();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ static public int staticGetMemoryClass() {
+ // Really brain dead right now -- just take this from the configured
+ // vm heap size, and assume it is in megabytes and thus ends with "m".
+ String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
+ if (vmHeapSize != null && !"".equals(vmHeapSize)) {
+ return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
+ }
+ return staticGetLargeMemoryClass();
+ }
+
+ /**
+ * Return the approximate per-application memory class of the current
+ * device when an application is running with a large heap. This is the
+ * space available for memory-intensive applications; most applications
+ * should not need this amount of memory, and should instead stay with the
+ * {@link #getMemoryClass()} limit. The returned value is in megabytes.
+ * This may be the same size as {@link #getMemoryClass()} on memory
+ * constrained devices, or it may be significantly larger on devices with
+ * a large amount of available RAM.
+ *
+ * <p>This is the size of the application's Dalvik heap if it has
+ * specified <code>android:largeHeap="true"</code> in its manifest.
+ */
+ public int getLargeMemoryClass() {
+ return staticGetLargeMemoryClass();
+ }
+
+ /** @hide */
+ static public int staticGetLargeMemoryClass() {
+ // Really brain dead right now -- just take this from the configured
+ // vm heap size, and assume it is in megabytes and thus ends with "m".
+ String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
+ return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
+ }
+
+ /**
+ * Returns true if this is a low-RAM device. Exactly whether a device is low-RAM
+ * is ultimately up to the device configuration, but currently it generally means
+ * something with 1GB or less of RAM. This is mostly intended to be used by apps
+ * to determine whether they should turn off certain features that require more RAM.
+ */
+ public boolean isLowRamDevice() {
+ return isLowRamDeviceStatic();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static boolean isLowRamDeviceStatic() {
+ return RoSystemProperties.CONFIG_LOW_RAM ||
+ (Build.IS_DEBUGGABLE && DEVELOPMENT_FORCE_LOW_RAM);
+ }
+
+ /**
+ * Returns true if this is a small battery device. Exactly whether a device is considered to be
+ * small battery is ultimately up to the device configuration, but currently it generally means
+ * something in the class of a device with 1000 mAh or less. This is mostly intended to be used
+ * to determine whether certain features should be altered to account for a drastically smaller
+ * battery.
+ * @hide
+ */
+ public static boolean isSmallBatteryDevice() {
+ return RoSystemProperties.CONFIG_SMALL_BATTERY;
+ }
+
+ /**
+ * Used by persistent processes to determine if they are running on a
+ * higher-end device so should be okay using hardware drawing acceleration
+ * (which tends to consume a lot more RAM).
+ * @hide
+ */
+ @TestApi
+ static public boolean isHighEndGfx() {
+ return !isLowRamDeviceStatic()
+ && !RoSystemProperties.CONFIG_AVOID_GFX_ACCEL
+ && !Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_avoidGfxAccel);
+ }
+
+ /**
+ * Return the total number of bytes of RAM this device has.
+ * @hide
+ */
+ @TestApi
+ public long getTotalRam() {
+ MemInfoReader memreader = new MemInfoReader();
+ memreader.readMemInfo();
+ return memreader.getTotalSize();
+ }
+
+ /**
+ * TODO(b/80414790): Remove once no longer on hiddenapi-light-greylist.txt
+ * @hide
+ * @deprecated Use {@link ActivityTaskManager#getMaxRecentTasksStatic()}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ static public int getMaxRecentTasksStatic() {
+ return ActivityTaskManager.getMaxRecentTasksStatic();
+ }
+
+ /**
+ * Information you can set and retrieve about the current activity within the recent task list.
+ */
+ public static class TaskDescription implements Parcelable {
+ /** @hide */
+ public static final String ATTR_TASKDESCRIPTION_PREFIX = "task_description_";
+ private static final String ATTR_TASKDESCRIPTIONLABEL =
+ ATTR_TASKDESCRIPTION_PREFIX + "label";
+ private static final String ATTR_TASKDESCRIPTIONCOLOR_PRIMARY =
+ ATTR_TASKDESCRIPTION_PREFIX + "color";
+ private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
+ ATTR_TASKDESCRIPTION_PREFIX + "color_background";
+ private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
+ ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+ private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
+ ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
+ private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE_PACKAGE =
+ ATTR_TASKDESCRIPTION_PREFIX + "icon_package";
+ private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING =
+ ATTR_TASKDESCRIPTION_PREFIX + "color_background_floating";
+
+ private String mLabel;
+ @Nullable
+ private Icon mIcon;
+ private String mIconFilename;
+ private int mColorPrimary;
+ private int mColorBackground;
+ private int mColorBackgroundFloating;
+ private int mStatusBarColor;
+ private int mNavigationBarColor;
+ private boolean mEnsureStatusBarContrastWhenTransparent;
+ private boolean mEnsureNavigationBarContrastWhenTransparent;
+ private int mResizeMode;
+ private int mMinWidth;
+ private int mMinHeight;
+
+ /**
+ * Provides a convenient way to set the fields of a {@link TaskDescription} when creating a
+ * new instance.
+ */
+ public static final class Builder {
+ /**
+ * Default values for the TaskDescription
+ */
+ @Nullable
+ private String mLabel = null;
+ @DrawableRes
+ private int mIconRes = Resources.ID_NULL;
+ private int mPrimaryColor = 0;
+ private int mBackgroundColor = 0;
+ private int mStatusBarColor = 0;
+ private int mNavigationBarColor = 0;
+
+ /**
+ * Set the label to use in the TaskDescription.
+ * @param label A label and description of the current state of this activity.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setLabel(@Nullable String label) {
+ this.mLabel = label;
+ return this;
+ }
+
+ /**
+ * Set the drawable resource of the icon to use in the TaskDescription.
+ * @param iconRes A drawable resource of an icon that represents the current state of
+ * this activity.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setIcon(@DrawableRes int iconRes) {
+ this.mIconRes = iconRes;
+ return this;
+ }
+
+ /**
+ * Set the primary color to use in the TaskDescription.
+ * @param color A color to override the theme's primary color. The color must be opaque.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setPrimaryColor(@ColorInt int color) {
+ this.mPrimaryColor = color;
+ return this;
+ }
+
+ /**
+ * Set the background color to use in the TaskDescription.
+ * @param color A color to override the theme's background color. The color must be
+ * opaque.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setBackgroundColor(@ColorInt int color) {
+ this.mBackgroundColor = color;
+ return this;
+ }
+
+ /**
+ * Set the status bar color to use in the TaskDescription.
+ * @param color A color to override the theme's status bar color.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setStatusBarColor(@ColorInt int color) {
+ this.mStatusBarColor = color;
+ return this;
+ }
+
+ /**
+ * Set the navigation bar color to use in the TaskDescription.
+ * @param color A color to override the theme's navigation bar color.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public Builder setNavigationBarColor(@ColorInt int color) {
+ this.mNavigationBarColor = color;
+ return this;
+ }
+
+ /**
+ * Build the TaskDescription.
+ * @return the TaskDescription object.
+ */
+ @NonNull
+ public TaskDescription build() {
+ final Icon icon = mIconRes == Resources.ID_NULL ? null :
+ Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
+ return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
+ mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
+ -1, -1, 0);
+ }
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this task.
+ * @param iconRes A drawable resource of an icon that represents the current state of this
+ * activity.
+ * @param colorPrimary A color to override the theme's primary color. This color must be
+ * opaque.
+ *
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
+ this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
+ colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+ throw new RuntimeException("A TaskDescription's primary color should be opaque");
+ }
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ * @param iconRes A drawable resource of an icon that represents the current state of this
+ * activity.
+ *
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public TaskDescription(String label, @DrawableRes int iconRes) {
+ this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
+ 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ *
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public TaskDescription(String label) {
+ this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ }
+
+ /**
+ * Creates an empty TaskDescription.
+ *
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public TaskDescription() {
+ this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this task.
+ * @param icon An icon that represents the current state of this task.
+ * @param colorPrimary A color to override the theme's primary color. This color must be
+ * opaque.
+ *
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public TaskDescription(String label, Bitmap icon, int colorPrimary) {
+ this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
+ false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+ throw new RuntimeException("A TaskDescription's primary color should be opaque");
+ }
+ }
+
+ /**
+ * Creates the TaskDescription to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ * @param icon An icon that represents the current state of this activity.
+ *
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public TaskDescription(String label, Bitmap icon) {
+ this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
+ RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ }
+
+ /** @hide */
+ public TaskDescription(@Nullable String label, @Nullable Icon icon,
+ int colorPrimary, int colorBackground,
+ int statusBarColor, int navigationBarColor,
+ boolean ensureStatusBarContrastWhenTransparent,
+ boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
+ int minHeight, int colorBackgroundFloating) {
+ mLabel = label;
+ mIcon = icon;
+ mColorPrimary = colorPrimary;
+ mColorBackground = colorBackground;
+ mStatusBarColor = statusBarColor;
+ mNavigationBarColor = navigationBarColor;
+ mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
+ mEnsureNavigationBarContrastWhenTransparent =
+ ensureNavigationBarContrastWhenTransparent;
+ mResizeMode = resizeMode;
+ mMinWidth = minWidth;
+ mMinHeight = minHeight;
+ mColorBackgroundFloating = colorBackgroundFloating;
+ }
+
+ /**
+ * Creates a copy of another TaskDescription.
+ */
+ public TaskDescription(TaskDescription td) {
+ copyFrom(td);
+ }
+
+ /**
+ * Copies this the values from another TaskDescription.
+ * @hide
+ */
+ public void copyFrom(TaskDescription other) {
+ mLabel = other.mLabel;
+ mIcon = other.mIcon;
+ mIconFilename = other.mIconFilename;
+ mColorPrimary = other.mColorPrimary;
+ mColorBackground = other.mColorBackground;
+ mStatusBarColor = other.mStatusBarColor;
+ mNavigationBarColor = other.mNavigationBarColor;
+ mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
+ mEnsureNavigationBarContrastWhenTransparent =
+ other.mEnsureNavigationBarContrastWhenTransparent;
+ mResizeMode = other.mResizeMode;
+ mMinWidth = other.mMinWidth;
+ mMinHeight = other.mMinHeight;
+ mColorBackgroundFloating = other.mColorBackgroundFloating;
+ }
+
+ /**
+ * Copies values from another TaskDescription, but preserves the hidden fields if they
+ * weren't set on {@code other}. Public fields will be overwritten anyway.
+ * @hide
+ */
+ public void copyFromPreserveHiddenFields(TaskDescription other) {
+ mLabel = other.mLabel;
+ mIcon = other.mIcon;
+ mIconFilename = other.mIconFilename;
+ mColorPrimary = other.mColorPrimary;
+
+ if (other.mColorBackground != 0) {
+ mColorBackground = other.mColorBackground;
+ }
+ if (other.mStatusBarColor != 0) {
+ mStatusBarColor = other.mStatusBarColor;
+ }
+ if (other.mNavigationBarColor != 0) {
+ mNavigationBarColor = other.mNavigationBarColor;
+ }
+
+ mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
+ mEnsureNavigationBarContrastWhenTransparent =
+ other.mEnsureNavigationBarContrastWhenTransparent;
+
+ if (other.mResizeMode != RESIZE_MODE_RESIZEABLE) {
+ mResizeMode = other.mResizeMode;
+ }
+ if (other.mMinWidth != -1) {
+ mMinWidth = other.mMinWidth;
+ }
+ if (other.mMinHeight != -1) {
+ mMinHeight = other.mMinHeight;
+ }
+ if (other.mColorBackgroundFloating != 0) {
+ mColorBackgroundFloating = other.mColorBackgroundFloating;
+ }
+ }
+
+ private TaskDescription(Parcel source) {
+ readFromParcel(source);
+ }
+
+ /**
+ * Sets the label for this task description.
+ * @hide
+ */
+ public void setLabel(String label) {
+ mLabel = label;
+ }
+
+ /**
+ * Sets the primary color for this task description.
+ * @hide
+ */
+ public void setPrimaryColor(int primaryColor) {
+ // Ensure that the given color is valid
+ if ((primaryColor != 0) && (Color.alpha(primaryColor) != 255)) {
+ throw new RuntimeException("A TaskDescription's primary color should be opaque");
+ }
+ mColorPrimary = primaryColor;
+ }
+
+ /**
+ * Sets the background color for this task description.
+ * @hide
+ */
+ public void setBackgroundColor(int backgroundColor) {
+ // Ensure that the given color is valid
+ if ((backgroundColor != 0) && (Color.alpha(backgroundColor) != 255)) {
+ throw new RuntimeException("A TaskDescription's background color should be opaque");
+ }
+ mColorBackground = backgroundColor;
+ }
+
+ /**
+ * Sets the background color floating for this task description.
+ * @hide
+ */
+ public void setBackgroundColorFloating(int backgroundColor) {
+ // Ensure that the given color is valid
+ if ((backgroundColor != 0) && (Color.alpha(backgroundColor) != 255)) {
+ throw new RuntimeException(
+ "A TaskDescription's background color floating should be opaque");
+ }
+ mColorBackgroundFloating = backgroundColor;
+ }
+
+ /**
+ * @hide
+ */
+ public void setStatusBarColor(int statusBarColor) {
+ mStatusBarColor = statusBarColor;
+ }
+
+ /**
+ * @hide
+ */
+ public void setNavigationBarColor(int navigationBarColor) {
+ mNavigationBarColor = navigationBarColor;
+ }
+
+ /**
+ * Sets the icon resource for this task description.
+ * @hide
+ */
+ public void setIcon(Icon icon) {
+ mIcon = icon;
+ }
+
+ /**
+ * Moves the icon bitmap reference from an actual Bitmap to a file containing the
+ * bitmap.
+ * @hide
+ */
+ public void setIconFilename(String iconFilename) {
+ mIconFilename = iconFilename;
+ if (iconFilename != null) {
+ // Only reset the icon if an actual persisted icon filepath was set
+ mIcon = null;
+ }
+ }
+
+ /**
+ * Sets the resize mode for this task description. Resize mode as in
+ * {@link android.content.pm.ActivityInfo}.
+ * @hide
+ */
+ public void setResizeMode(int resizeMode) {
+ mResizeMode = resizeMode;
+ }
+
+ /**
+ * The minimal width size to show the app content in freeform mode.
+ * @param minWidth minimal width, -1 for system default.
+ * @hide
+ */
+ public void setMinWidth(int minWidth) {
+ mMinWidth = minWidth;
+ }
+
+ /**
+ * The minimal height size to show the app content in freeform mode.
+ * @param minHeight minimal height, -1 for system default.
+ * @hide
+ */
+ public void setMinHeight(int minHeight) {
+ mMinHeight = minHeight;
+ }
+
+ /**
+ * @return The label and description of the current state of this task.
+ */
+ public String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * @return The actual icon that represents the current state of this task if it is in memory
+ * or loads it from disk if available.
+ * @hide
+ */
+ public Icon loadIcon() {
+ if (mIcon != null) {
+ return mIcon;
+ }
+ Bitmap loadedIcon = loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
+ if (loadedIcon != null) {
+ return Icon.createWithBitmap(loadedIcon);
+ }
+ return null;
+ }
+
+ /**
+ * @return The in-memory or loaded icon that represents the current state of this task.
+ * @deprecated This call is no longer supported. The caller should keep track of any icons
+ * it sets for the task descriptions internally.
+ */
+ @Deprecated
+ public Bitmap getIcon() {
+ Bitmap icon = getInMemoryIcon();
+ if (icon != null) {
+ return icon;
+ }
+ return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ @Nullable
+ public Icon getRawIcon() {
+ return mIcon;
+ }
+
+ /** @hide */
+ @TestApi
+ @Nullable
+ public String getIconResourcePackage() {
+ if (mIcon != null && mIcon.getType() == Icon.TYPE_RESOURCE) {
+ return mIcon.getResPackage();
+ }
+ return "";
+ }
+
+ /** @hide */
+ @TestApi
+ public int getIconResource() {
+ if (mIcon != null && mIcon.getType() == Icon.TYPE_RESOURCE) {
+ return mIcon.getResId();
+ }
+ return 0;
+ }
+
+ /** @hide */
+ @TestApi
+ public String getIconFilename() {
+ return mIconFilename;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public Bitmap getInMemoryIcon() {
+ if (mIcon != null && mIcon.getType() == Icon.TYPE_BITMAP) {
+ return mIcon.getBitmap();
+ }
+ return null;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) {
+ if (iconFilename != null) {
+ try {
+ return getTaskService().getTaskDescriptionIcon(iconFilename,
+ userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return The color override on the theme's primary color.
+ */
+ @ColorInt
+ public int getPrimaryColor() {
+ return mColorPrimary;
+ }
+
+ /**
+ * @return The color override on the theme's background color.
+ */
+ @ColorInt
+ public int getBackgroundColor() {
+ return mColorBackground;
+ }
+
+ /**
+ * @return The background color floating.
+ * @hide
+ */
+ public int getBackgroundColorFloating() {
+ return mColorBackgroundFloating;
+ }
+
+ /**
+ * @return The color override on the theme's status bar color.
+ */
+ @ColorInt
+ public int getStatusBarColor() {
+ return mStatusBarColor;
+ }
+
+ /**
+ * @return The color override on the theme's navigation bar color.
+ */
+ @ColorInt
+ public int getNavigationBarColor() {
+ return mNavigationBarColor;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getEnsureStatusBarContrastWhenTransparent() {
+ return mEnsureStatusBarContrastWhenTransparent;
+ }
+
+ /**
+ * @hide
+ */
+ public void setEnsureStatusBarContrastWhenTransparent(
+ boolean ensureStatusBarContrastWhenTransparent) {
+ mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getEnsureNavigationBarContrastWhenTransparent() {
+ return mEnsureNavigationBarContrastWhenTransparent;
+ }
+
+ /**
+ * @hide
+ */
+ public void setEnsureNavigationBarContrastWhenTransparent(
+ boolean ensureNavigationBarContrastWhenTransparent) {
+ mEnsureNavigationBarContrastWhenTransparent =
+ ensureNavigationBarContrastWhenTransparent;
+ }
+
+ /**
+ * @hide
+ */
+ public int getResizeMode() {
+ return mResizeMode;
+ }
+
+ /**
+ * @hide
+ */
+ public int getMinWidth() {
+ return mMinWidth;
+ }
+
+ /**
+ * @hide
+ */
+ public int getMinHeight() {
+ return mMinHeight;
+ }
+
+ /** @hide */
+ public void saveToXml(TypedXmlSerializer out) throws IOException {
+ if (mLabel != null) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, mLabel);
+ }
+ if (mColorPrimary != 0) {
+ out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_PRIMARY, mColorPrimary);
+ }
+ if (mColorBackground != 0) {
+ out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND, mColorBackground);
+ }
+ if (mColorBackgroundFloating != 0) {
+ out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING,
+ mColorBackgroundFloating);
+ }
+ if (mIconFilename != null) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
+ }
+ if (mIcon != null && mIcon.getType() == Icon.TYPE_RESOURCE) {
+ out.attributeInt(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, mIcon.getResId());
+ out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE_PACKAGE,
+ mIcon.getResPackage());
+ }
+ }
+
+ /** @hide */
+ public void restoreFromXml(TypedXmlPullParser in) {
+ final String label = in.getAttributeValue(null, ATTR_TASKDESCRIPTIONLABEL);
+ if (label != null) {
+ setLabel(label);
+ }
+ final int colorPrimary = in.getAttributeIntHex(null,
+ ATTR_TASKDESCRIPTIONCOLOR_PRIMARY, 0);
+ if (colorPrimary != 0) {
+ setPrimaryColor(colorPrimary);
+ }
+ final int colorBackground = in.getAttributeIntHex(null,
+ ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND, 0);
+ if (colorBackground != 0) {
+ setBackgroundColor(colorBackground);
+ }
+ final int colorBackgroundFloating = in.getAttributeIntHex(null,
+ ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING, 0);
+ if (colorBackgroundFloating != 0) {
+ setBackgroundColorFloating(colorBackgroundFloating);
+ }
+ final String iconFilename = in.getAttributeValue(null,
+ ATTR_TASKDESCRIPTIONICON_FILENAME);
+ if (iconFilename != null) {
+ setIconFilename(iconFilename);
+ }
+ final int iconResourceId = in.getAttributeInt(null,
+ ATTR_TASKDESCRIPTIONICON_RESOURCE, Resources.ID_NULL);
+ final String iconResourcePackage = in.getAttributeValue(null,
+ ATTR_TASKDESCRIPTIONICON_RESOURCE_PACKAGE);
+ if (iconResourceId != Resources.ID_NULL && iconResourcePackage != null) {
+ setIcon(Icon.createWithResource(iconResourcePackage,
+ iconResourceId));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mLabel == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeString(mLabel);
+ }
+ final Bitmap bitmapIcon = getInMemoryIcon();
+ if (mIcon == null || (bitmapIcon != null && bitmapIcon.isRecycled())) {
+ // If there is no icon, or if the icon is a bitmap that has been recycled, then
+ // don't write anything to disk
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ mIcon.writeToParcel(dest, 0);
+ }
+ dest.writeInt(mColorPrimary);
+ dest.writeInt(mColorBackground);
+ dest.writeInt(mStatusBarColor);
+ dest.writeInt(mNavigationBarColor);
+ dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
+ dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
+ dest.writeInt(mResizeMode);
+ dest.writeInt(mMinWidth);
+ dest.writeInt(mMinHeight);
+ if (mIconFilename == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeString(mIconFilename);
+ }
+ dest.writeInt(mColorBackgroundFloating);
+ }
+
+ public void readFromParcel(Parcel source) {
+ mLabel = source.readInt() > 0 ? source.readString() : null;
+ if (source.readInt() > 0) {
+ mIcon = Icon.CREATOR.createFromParcel(source);
+ }
+ mColorPrimary = source.readInt();
+ mColorBackground = source.readInt();
+ mStatusBarColor = source.readInt();
+ mNavigationBarColor = source.readInt();
+ mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
+ mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
+ mResizeMode = source.readInt();
+ mMinWidth = source.readInt();
+ mMinHeight = source.readInt();
+ mIconFilename = source.readInt() > 0 ? source.readString() : null;
+ mColorBackgroundFloating = source.readInt();
+ }
+
+ public static final @android.annotation.NonNull Creator<TaskDescription> CREATOR
+ = new Creator<TaskDescription>() {
+ public TaskDescription createFromParcel(Parcel source) {
+ return new TaskDescription(source);
+ }
+ public TaskDescription[] newArray(int size) {
+ return new TaskDescription[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "TaskDescription Label: " + mLabel + " Icon: " + mIcon
+ + " IconFilename: " + mIconFilename
+ + " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground
+ + " statusBarColor: " + mStatusBarColor
+ + (mEnsureStatusBarContrastWhenTransparent ? " (contrast when transparent)"
+ : "") + " navigationBarColor: " + mNavigationBarColor
+ + (mEnsureNavigationBarContrastWhenTransparent
+ ? " (contrast when transparent)" : "")
+ + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
+ + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
+ + " colorBackgrounFloating: " + mColorBackgroundFloating;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TaskDescription)) {
+ return false;
+ }
+
+ TaskDescription other = (TaskDescription) obj;
+ return TextUtils.equals(mLabel, other.mLabel)
+ && TextUtils.equals(mIconFilename, other.mIconFilename)
+ && mIcon == other.mIcon
+ && mColorPrimary == other.mColorPrimary
+ && mColorBackground == other.mColorBackground
+ && mStatusBarColor == other.mStatusBarColor
+ && mNavigationBarColor == other.mNavigationBarColor
+ && mEnsureStatusBarContrastWhenTransparent
+ == other.mEnsureStatusBarContrastWhenTransparent
+ && mEnsureNavigationBarContrastWhenTransparent
+ == other.mEnsureNavigationBarContrastWhenTransparent
+ && mResizeMode == other.mResizeMode
+ && mMinWidth == other.mMinWidth
+ && mMinHeight == other.mMinHeight
+ && mColorBackgroundFloating == other.mColorBackgroundFloating;
+ }
+
+ /** @hide */
+ public static boolean equals(TaskDescription td1, TaskDescription td2) {
+ if (td1 == null && td2 == null) {
+ return true;
+ } else if (td1 != null && td2 != null) {
+ return td1.equals(td2);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Information you can retrieve about tasks that the user has most recently
+ * started or visited.
+ */
+ public static class RecentTaskInfo extends TaskInfo implements Parcelable {
+ /**
+ * @hide
+ */
+ public static class PersistedTaskSnapshotData {
+ /**
+ * The bounds of the task when the last snapshot was taken, may be null if the task is
+ * not yet attached to the hierarchy.
+ * @see {@link android.window.TaskSnapshot#mTaskSize}.
+ * @hide
+ */
+ public @Nullable Point taskSize;
+
+ /**
+ * The content insets of the task when the task snapshot was taken.
+ * @see {@link android.window.TaskSnapshot#mContentInsets}.
+ * @hide
+ */
+ public @Nullable Rect contentInsets;
+
+ /**
+ * The size of the last snapshot taken, may be null if there is no associated snapshot.
+ * @see {@link android.window.TaskSnapshot#mSnapshot}.
+ * @hide
+ */
+ public @Nullable Point bufferSize;
+
+ /**
+ * Sets the data from the other data.
+ * @hide
+ */
+ public void set(PersistedTaskSnapshotData other) {
+ taskSize = other.taskSize;
+ contentInsets = other.contentInsets;
+ bufferSize = other.bufferSize;
+ }
+
+ /**
+ * Sets the data from the provided {@param snapshot}.
+ * @hide
+ */
+ public void set(TaskSnapshot snapshot) {
+ if (snapshot == null) {
+ taskSize = null;
+ contentInsets = null;
+ bufferSize = null;
+ return;
+ }
+ final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+ taskSize = new Point(snapshot.getTaskSize());
+ contentInsets = new Rect(snapshot.getContentInsets());
+ bufferSize = buffer != null
+ ? new Point(buffer.getWidth(), buffer.getHeight())
+ : null;
+ }
+ }
+
+ /**
+ * If this task is currently running, this is the identifier for it.
+ * If it is not running, this will be -1.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, use
+ * {@link RecentTaskInfo#taskId} to get the task id and {@link RecentTaskInfo#isRunning}
+ * to determine if it is running.
+ */
+ @Deprecated
+ public int id;
+
+ /**
+ * The true identifier of this task, valid even if it is not running.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, use
+ * {@link RecentTaskInfo#taskId}.
+ */
+ @Deprecated
+ public int persistentId;
+
+ /**
+ * Description of the task's last state.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always null.
+ */
+ @Deprecated
+ public CharSequence description;
+
+ /**
+ * Task affiliation for grouping with other tasks.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always 0.
+ */
+ @Deprecated
+ public int affiliatedTaskId;
+
+ /**
+ * Information of organized child tasks.
+ *
+ * @hide
+ */
+ public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
+
+ /**
+ * Information about the last snapshot taken for this task.
+ * @hide
+ */
+ public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
+
+ public RecentTaskInfo() {
+ }
+
+ private RecentTaskInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel source) {
+ id = source.readInt();
+ persistentId = source.readInt();
+ childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class);
+ lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
+ lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
+ lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
+ super.readFromParcel(source);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ dest.writeInt(persistentId);
+ dest.writeList(childrenTaskInfos);
+ dest.writeTypedObject(lastSnapshotData.taskSize, flags);
+ dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
+ dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
+ super.writeToParcel(dest, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR
+ = new Creator<RecentTaskInfo>() {
+ public RecentTaskInfo createFromParcel(Parcel source) {
+ return new RecentTaskInfo(source);
+ }
+ public RecentTaskInfo[] newArray(int size) {
+ return new RecentTaskInfo[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public void dump(PrintWriter pw, String indent) {
+ pw.println(); pw.print(" ");
+ pw.print(" id="); pw.print(persistentId);
+ pw.print(" userId="); pw.print(userId);
+ pw.print(" hasTask="); pw.print((id != -1));
+ pw.print(" lastActiveTime="); pw.println(lastActiveTime);
+ pw.print(" "); pw.print(" baseIntent="); pw.println(baseIntent);
+ if (baseActivity != null) {
+ pw.print(" "); pw.print(" baseActivity=");
+ pw.println(baseActivity.toShortString());
+ }
+ if (topActivity != null) {
+ pw.print(" "); pw.print(" topActivity="); pw.println(topActivity.toShortString());
+ }
+ if (origActivity != null) {
+ pw.print(" "); pw.print(" origActivity=");
+ pw.println(origActivity.toShortString());
+ }
+ if (realActivity != null) {
+ pw.print(" "); pw.print(" realActivity=");
+ pw.println(realActivity.toShortString());
+ }
+ pw.print(" ");
+ pw.print(" isExcluded=");
+ pw.print(((baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0));
+ pw.print(" activityType="); pw.print(activityTypeToString(getActivityType()));
+ pw.print(" windowingMode="); pw.print(windowingModeToString(getWindowingMode()));
+ pw.print(" supportsMultiWindow=");
+ pw.println(supportsMultiWindow);
+ if (taskDescription != null) {
+ pw.print(" ");
+ final ActivityManager.TaskDescription td = taskDescription;
+ pw.print(" taskDescription {");
+ pw.print(" colorBackground=#");
+ pw.print(Integer.toHexString(td.getBackgroundColor()));
+ pw.print(" colorPrimary=#");
+ pw.print(Integer.toHexString(td.getPrimaryColor()));
+ pw.print(" iconRes=");
+ pw.print(td.getIconResourcePackage() + "/" + td.getIconResource());
+ pw.print(" iconBitmap=");
+ pw.print(td.getIconFilename() != null || td.getInMemoryIcon() != null);
+ pw.print(" resizeMode=");
+ pw.print(ActivityInfo.resizeModeToString(td.getResizeMode()));
+ pw.print(" minWidth="); pw.print(td.getMinWidth());
+ pw.print(" minHeight="); pw.print(td.getMinHeight());
+ pw.print(" colorBackgroundFloating=#");
+ pw.print(Integer.toHexString(td.getBackgroundColorFloating()));
+ pw.println(" }");
+ }
+ pw.print(" ");
+ pw.print(" lastSnapshotData {");
+ pw.print(" taskSize=" + lastSnapshotData.taskSize);
+ pw.print(" contentInsets=" + lastSnapshotData.contentInsets);
+ pw.print(" bufferSize=" + lastSnapshotData.bufferSize);
+ pw.println(" }");
+ }
+ }
+
+ /**
+ * Flag for use with {@link #getRecentTasks}: return all tasks, even those
+ * that have set their
+ * {@link android.content.Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag.
+ */
+ public static final int RECENT_WITH_EXCLUDED = 0x0001;
+
+ /**
+ * Provides a list that does not contain any
+ * recent tasks that currently are not available to the user.
+ */
+ public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
+
+ /**
+ * <p></p>Return a list of the tasks that the user has recently launched, with
+ * the most recent being first and older ones after in order.
+ *
+ * <p><b>Note: this method is only intended for debugging and presenting
+ * task management user interfaces</b>. This should never be used for
+ * core logic in an application, such as deciding between different
+ * behaviors based on the information found here. Such uses are
+ * <em>not</em> supported, and will likely break in the future. For
+ * example, if multiple applications can be actively running at the
+ * same time, assumptions made about the meaning of the data here for
+ * purposes of control flow will be incorrect.</p>
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method is
+ * no longer available to third party applications: the introduction of
+ * document-centric recents means
+ * it can leak personal information to the caller. For backwards compatibility,
+ * it will still return a small subset of its data: at least the caller's
+ * own tasks (though see {@link #getAppTasks()} for the correct supported
+ * way to retrieve that information), and possibly some other tasks
+ * such as home that are known to not be sensitive.
+ *
+ * @param maxNum The maximum number of entries to return in the list. The
+ * actual number returned may be smaller, depending on how many tasks the
+ * user has started and the maximum number the system can remember.
+ * @param flags Information about what to return. May be any combination
+ * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
+ *
+ * @return Returns a list of RecentTaskInfo records describing each of
+ * the recent tasks.
+ */
+ @Deprecated
+ public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) throws SecurityException {
+ if (maxNum < 0) {
+ throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+ }
+ return ActivityTaskManager.getInstance().getRecentTasks(
+ maxNum, flags, mContext.getUserId());
+ }
+
+ /**
+ * Information you can retrieve about a particular task that is currently
+ * "running" in the system. Note that a running task does not mean the
+ * given task actually has a process it is actively running in; it simply
+ * means that the user has gone to it and never closed it, but currently
+ * the system may have killed its process and is only holding on to its
+ * last state in order to restart it when the user returns.
+ */
+ public static class RunningTaskInfo extends TaskInfo implements Parcelable {
+
+ /**
+ * A unique identifier for this task.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, use
+ * {@link RunningTaskInfo#taskId}.
+ */
+ @Deprecated
+ public int id;
+
+ /**
+ * Thumbnail representation of the task's current state.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always null.
+ */
+ @Deprecated
+ public Bitmap thumbnail;
+
+ /**
+ * Description of the task's current state.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always null.
+ */
+ @Deprecated
+ public CharSequence description;
+
+ /**
+ * Number of activities that are currently running (not stopped and persisted) in this task.
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always 0.
+ */
+ @Deprecated
+ public int numRunning;
+
+ public RunningTaskInfo() {
+ }
+
+ private RunningTaskInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel source) {
+ id = source.readInt();
+ super.readFromParcel(source);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(id);
+ super.writeToParcel(dest, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
+ public RunningTaskInfo createFromParcel(Parcel source) {
+ return new RunningTaskInfo(source);
+ }
+ public RunningTaskInfo[] newArray(int size) {
+ return new RunningTaskInfo[size];
+ }
+ };
+ }
+
+ /**
+ * Get the list of tasks associated with the calling application.
+ *
+ * @return The list of tasks associated with the application making this call.
+ * @throws SecurityException
+ */
+ public List<ActivityManager.AppTask> getAppTasks() {
+ ArrayList<AppTask> tasks = new ArrayList<AppTask>();
+ List<IBinder> appTasks;
+ try {
+ appTasks = getTaskService().getAppTasks(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ int numAppTasks = appTasks.size();
+ for (int i = 0; i < numAppTasks; i++) {
+ tasks.add(new AppTask(IAppTask.Stub.asInterface(appTasks.get(i))));
+ }
+ return tasks;
+ }
+
+ /**
+ * Return the current design dimensions for {@link AppTask} thumbnails, for use
+ * with {@link #addAppTask}.
+ */
+ public Size getAppTaskThumbnailSize() {
+ synchronized (this) {
+ ensureAppTaskThumbnailSizeLocked();
+ return new Size(mAppTaskThumbnailSize.x, mAppTaskThumbnailSize.y);
+ }
+ }
+
+ private void ensureAppTaskThumbnailSizeLocked() {
+ if (mAppTaskThumbnailSize == null) {
+ try {
+ mAppTaskThumbnailSize = getTaskService().getAppTaskThumbnailSize();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Add a new {@link AppTask} for the calling application. This will create a new
+ * recents entry that is added to the <b>end</b> of all existing recents.
+ *
+ * @param activity The activity that is adding the entry. This is used to help determine
+ * the context that the new recents entry will be in.
+ * @param intent The Intent that describes the recents entry. This is the same Intent that
+ * you would have used to launch the activity for it. In generally you will want to set
+ * both {@link Intent#FLAG_ACTIVITY_NEW_DOCUMENT} and
+ * {@link Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}; the latter is required since this recents
+ * entry will exist without an activity, so it doesn't make sense to not retain it when
+ * its activity disappears. The given Intent here also must have an explicit ComponentName
+ * set on it.
+ * @param description Optional additional description information.
+ * @param thumbnail Thumbnail to use for the recents entry. Should be the size given by
+ * {@link #getAppTaskThumbnailSize()}. If the bitmap is not that exact size, it will be
+ * recreated in your process, probably in a way you don't like, before the recents entry
+ * is added.
+ *
+ * @return Returns the task id of the newly added app task, or -1 if the add failed. The
+ * most likely cause of failure is that there is no more room for more tasks for your app.
+ */
+ public int addAppTask(@NonNull Activity activity, @NonNull Intent intent,
+ @Nullable TaskDescription description, @NonNull Bitmap thumbnail) {
+ Point size;
+ synchronized (this) {
+ ensureAppTaskThumbnailSizeLocked();
+ size = mAppTaskThumbnailSize;
+ }
+ final int tw = thumbnail.getWidth();
+ final int th = thumbnail.getHeight();
+ if (tw != size.x || th != size.y) {
+ Bitmap bm = Bitmap.createBitmap(size.x, size.y, thumbnail.getConfig());
+
+ // Use ScaleType.CENTER_CROP, except we leave the top edge at the top.
+ float scale;
+ float dx = 0, dy = 0;
+ if (tw * size.x > size.y * th) {
+ scale = (float) size.x / (float) th;
+ dx = (size.y - tw * scale) * 0.5f;
+ } else {
+ scale = (float) size.y / (float) tw;
+ dy = (size.x - th * scale) * 0.5f;
+ }
+ Matrix matrix = new Matrix();
+ matrix.setScale(scale, scale);
+ matrix.postTranslate((int) (dx + 0.5f), 0);
+
+ Canvas canvas = new Canvas(bm);
+ canvas.drawBitmap(thumbnail, matrix, null);
+ canvas.setBitmap(null);
+
+ thumbnail = bm;
+ }
+ if (description == null) {
+ description = new TaskDescription();
+ }
+ try {
+ return getTaskService().addAppTask(activity.getActivityToken(),
+ intent, description, thumbnail);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of the tasks that are currently running, with
+ * the most recent being first and older ones after in order. Note that
+ * "running" does not mean any of the task's code is currently loaded or
+ * activity -- the task may have been frozen by the system, so that it
+ * can be restarted in its previous state when next brought to the
+ * foreground.
+ *
+ * <p><b>Note: this method is only intended for debugging and presenting
+ * task management user interfaces</b>. This should never be used for
+ * core logic in an application, such as deciding between different
+ * behaviors based on the information found here. Such uses are
+ * <em>not</em> supported, and will likely break in the future. For
+ * example, if multiple applications can be actively running at the
+ * same time, assumptions made about the meaning of the data here for
+ * purposes of control flow will be incorrect.</p>
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method
+ * is no longer available to third party
+ * applications: the introduction of document-centric recents means
+ * it can leak person information to the caller. For backwards compatibility,
+ * it will still return a small subset of its data: at least the caller's
+ * own tasks, and possibly some other tasks
+ * such as home that are known to not be sensitive.
+ *
+ * @param maxNum The maximum number of entries to return in the list. The
+ * actual number returned may be smaller, depending on how many tasks the
+ * user has started.
+ *
+ * @return Returns a list of RunningTaskInfo records describing each of
+ * the running tasks.
+ */
+ @Deprecated
+ public List<RunningTaskInfo> getRunningTasks(int maxNum)
+ throws SecurityException {
+ return ActivityTaskManager.getInstance().getTasks(maxNum);
+ }
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "MOVE_TASK_" }, value = {
+ MOVE_TASK_WITH_HOME,
+ MOVE_TASK_NO_USER_ACTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MoveTaskFlags {}
+
+ /**
+ * Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
+ * activity along with the task, so it is positioned immediately behind
+ * the task.
+ */
+ public static final int MOVE_TASK_WITH_HOME = 0x00000001;
+
+ /**
+ * Flag for {@link #moveTaskToFront(int, int)}: don't count this as a
+ * user-instigated action, so the current activity will not receive a
+ * hint that the user is leaving.
+ */
+ public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002;
+
+ /**
+ * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)}
+ * with a null options argument.
+ *
+ * @param taskId The identifier of the task to be moved, as found in
+ * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
+ * @param flags Additional operational flags.
+ */
+ @RequiresPermission(android.Manifest.permission.REORDER_TASKS)
+ public void moveTaskToFront(int taskId, @MoveTaskFlags int flags) {
+ moveTaskToFront(taskId, flags, null);
+ }
+
+ /**
+ * Ask that the task associated with a given task ID be moved to the
+ * front of the stack, so it is now visible to the user.
+ *
+ * @param taskId The identifier of the task to be moved, as found in
+ * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
+ * @param flags Additional operational flags.
+ * @param options Additional options for the operation, either null or
+ * as per {@link Context#startActivity(Intent, android.os.Bundle)
+ * Context.startActivity(Intent, Bundle)}.
+ */
+ @RequiresPermission(android.Manifest.permission.REORDER_TASKS)
+ public void moveTaskToFront(int taskId, @MoveTaskFlags int flags, Bundle options) {
+ try {
+ ActivityThread thread = ActivityThread.currentActivityThread();
+ IApplicationThread appThread = thread.getApplicationThread();
+ String packageName = mContext.getOpPackageName();
+ getTaskService().moveTaskToFront(appThread, packageName, taskId, flags, options);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if the context is allowed to start an activity on specified display. Some launch
+ * restrictions may apply to secondary displays that are private, virtual, or owned by the
+ * system, in which case an activity start may throw a {@link SecurityException}. Call this
+ * method prior to starting an activity on a secondary display to check if the current context
+ * has access to it.
+ *
+ * @see ActivityOptions#setLaunchDisplayId(int)
+ * @see android.view.Display#FLAG_PRIVATE
+ *
+ * @param context Source context, from which an activity will be started.
+ * @param displayId Target display id.
+ * @param intent Intent used to launch an activity.
+ * @return {@code true} if a call to start an activity on the target display is allowed for the
+ * provided context and no {@link SecurityException} will be thrown, {@code false} otherwise.
+ */
+ public boolean isActivityStartAllowedOnDisplay(@NonNull Context context, int displayId,
+ @NonNull Intent intent) {
+ try {
+ return getTaskService().isActivityStartAllowedOnDisplay(displayId, intent,
+ intent.resolveTypeIfNeeded(context.getContentResolver()), context.getUserId());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
+ * Information you can retrieve about a particular Service that is
+ * currently running in the system.
+ */
+ public static class RunningServiceInfo implements Parcelable {
+ /**
+ * The service component.
+ */
+ public ComponentName service;
+
+ /**
+ * If non-zero, this is the process the service is running in.
+ */
+ public int pid;
+
+ /**
+ * The UID that owns this service.
+ */
+ public int uid;
+
+ /**
+ * The name of the process this service runs in.
+ */
+ public String process;
+
+ /**
+ * Set to true if the service has asked to run as a foreground process.
+ */
+ public boolean foreground;
+
+ /**
+ * The time when the service was first made active, either by someone
+ * starting or binding to it. This
+ * is in units of {@link android.os.SystemClock#elapsedRealtime()}.
+ */
+ public long activeSince;
+
+ /**
+ * Set to true if this service has been explicitly started.
+ */
+ public boolean started;
+
+ /**
+ * Number of clients connected to the service.
+ */
+ public int clientCount;
+
+ /**
+ * Number of times the service's process has crashed while the service
+ * is running.
+ */
+ public int crashCount;
+
+ /**
+ * The time when there was last activity in the service (either
+ * explicit requests to start it or clients binding to it). This
+ * is in units of {@link android.os.SystemClock#uptimeMillis()}.
+ */
+ public long lastActivityTime;
+
+ /**
+ * If non-zero, this service is not currently running, but scheduled to
+ * restart at the given time.
+ */
+ public long restarting;
+
+ /**
+ * Bit for {@link #flags}: set if this service has been
+ * explicitly started.
+ */
+ public static final int FLAG_STARTED = 1<<0;
+
+ /**
+ * Bit for {@link #flags}: set if the service has asked to
+ * run as a foreground process.
+ */
+ public static final int FLAG_FOREGROUND = 1<<1;
+
+ /**
+ * Bit for {@link #flags}: set if the service is running in a
+ * core system process.
+ */
+ public static final int FLAG_SYSTEM_PROCESS = 1<<2;
+
+ /**
+ * Bit for {@link #flags}: set if the service is running in a
+ * persistent process.
+ */
+ public static final int FLAG_PERSISTENT_PROCESS = 1<<3;
+
+ /**
+ * Running flags.
+ */
+ public int flags;
+
+ /**
+ * For special services that are bound to by system code, this is
+ * the package that holds the binding.
+ */
+ public String clientPackage;
+
+ /**
+ * For special services that are bound to by system code, this is
+ * a string resource providing a user-visible label for who the
+ * client is.
+ */
+ public int clientLabel;
+
+ public RunningServiceInfo() {
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ ComponentName.writeToParcel(service, dest);
+ dest.writeInt(pid);
+ dest.writeInt(uid);
+ dest.writeString(process);
+ dest.writeInt(foreground ? 1 : 0);
+ dest.writeLong(activeSince);
+ dest.writeInt(started ? 1 : 0);
+ dest.writeInt(clientCount);
+ dest.writeInt(crashCount);
+ dest.writeLong(lastActivityTime);
+ dest.writeLong(restarting);
+ dest.writeInt(this.flags);
+ dest.writeString(clientPackage);
+ dest.writeInt(clientLabel);
+ }
+
+ public void readFromParcel(Parcel source) {
+ service = ComponentName.readFromParcel(source);
+ pid = source.readInt();
+ uid = source.readInt();
+ process = source.readString();
+ foreground = source.readInt() != 0;
+ activeSince = source.readLong();
+ started = source.readInt() != 0;
+ clientCount = source.readInt();
+ crashCount = source.readInt();
+ lastActivityTime = source.readLong();
+ restarting = source.readLong();
+ flags = source.readInt();
+ clientPackage = source.readString();
+ clientLabel = source.readInt();
+ }
+
+ public static final @android.annotation.NonNull Creator<RunningServiceInfo> CREATOR = new Creator<RunningServiceInfo>() {
+ public RunningServiceInfo createFromParcel(Parcel source) {
+ return new RunningServiceInfo(source);
+ }
+ public RunningServiceInfo[] newArray(int size) {
+ return new RunningServiceInfo[size];
+ }
+ };
+
+ private RunningServiceInfo(Parcel source) {
+ readFromParcel(source);
+ }
+ }
+
+ /**
+ * Return a list of the services that are currently running.
+ *
+ * <p><b>Note: this method is only intended for debugging or implementing
+ * service management type user interfaces.</b></p>
+ *
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#O}, this method
+ * is no longer available to third party applications. For backwards compatibility,
+ * it will still return the caller's own services.
+ *
+ * @param maxNum The maximum number of entries to return in the list. The
+ * actual number returned may be smaller, depending on how many services
+ * are running.
+ *
+ * @return Returns a list of RunningServiceInfo records describing each of
+ * the running tasks.
+ */
+ @Deprecated
+ public List<RunningServiceInfo> getRunningServices(int maxNum)
+ throws SecurityException {
+ try {
+ return getService()
+ .getServices(maxNum, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a PendingIntent you can start to show a control panel for the
+ * given running service. If the service does not have a control panel,
+ * null is returned.
+ */
+ public PendingIntent getRunningServiceControlPanel(ComponentName service)
+ throws SecurityException {
+ try {
+ return getService()
+ .getRunningServiceControlPanel(service);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Information you can retrieve about the available memory through
+ * {@link ActivityManager#getMemoryInfo}.
+ */
+ public static class MemoryInfo implements Parcelable {
+ /**
+ * The advertised memory of the system, as the end user would encounter in a retail display
+ * environment. This value might be different from {@code totalMem}. This could be due to
+ * many reasons. For example, the ODM could reserve part of the memory for the Trusted
+ * Execution Environment (TEE) which the kernel doesn't have access or knowledge about it.
+ */
+ @SuppressLint("MutableBareField")
+ public long advertisedMem;
+
+ /**
+ * The available memory on the system. This number should not
+ * be considered absolute: due to the nature of the kernel, a significant
+ * portion of this memory is actually in use and needed for the overall
+ * system to run well.
+ */
+ public long availMem;
+
+ /**
+ * The total memory accessible by the kernel. This is basically the
+ * RAM size of the device, not including below-kernel fixed allocations
+ * like DMA buffers, RAM for the baseband CPU, etc.
+ */
+ public long totalMem;
+
+ /**
+ * The threshold of {@link #availMem} at which we consider memory to be
+ * low and start killing background services and other non-extraneous
+ * processes.
+ */
+ public long threshold;
+
+ /**
+ * Set to true if the system considers itself to currently be in a low
+ * memory situation.
+ */
+ public boolean lowMemory;
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long hiddenAppThreshold;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long secondaryServerThreshold;
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public long visibleAppThreshold;
+ /** @hide */
+ @UnsupportedAppUsage
+ public long foregroundAppThreshold;
+
+ public MemoryInfo() {
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(advertisedMem);
+ dest.writeLong(availMem);
+ dest.writeLong(totalMem);
+ dest.writeLong(threshold);
+ dest.writeInt(lowMemory ? 1 : 0);
+ dest.writeLong(hiddenAppThreshold);
+ dest.writeLong(secondaryServerThreshold);
+ dest.writeLong(visibleAppThreshold);
+ dest.writeLong(foregroundAppThreshold);
+ }
+
+ public void readFromParcel(Parcel source) {
+ advertisedMem = source.readLong();
+ availMem = source.readLong();
+ totalMem = source.readLong();
+ threshold = source.readLong();
+ lowMemory = source.readInt() != 0;
+ hiddenAppThreshold = source.readLong();
+ secondaryServerThreshold = source.readLong();
+ visibleAppThreshold = source.readLong();
+ foregroundAppThreshold = source.readLong();
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Return general information about the memory state of the system. This
+ * can be used to help decide how to manage your own memory, though note
+ * that polling is not recommended and
+ * {@link android.content.ComponentCallbacks2#onTrimMemory(int)
+ * ComponentCallbacks2.onTrimMemory(int)} is the preferred way to do this.
+ * Also see {@link #getMyMemoryState} for how to retrieve the current trim
+ * level of your process as needed, which gives a better hint for how to
+ * manage its memory.
+ */
+ public void getMemoryInfo(MemoryInfo outInfo) {
+ try {
+ getService().getMemoryInfo(outInfo);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(anyOf={Manifest.permission.CLEAR_APP_USER_DATA,
+ Manifest.permission.ACCESS_INSTANT_APPS})
+ @UnsupportedAppUsage
+ public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
+ try {
+ return getService().clearApplicationUserData(packageName, false,
+ observer, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Permits an application to erase its own data from disk. This is equivalent to
+ * the user choosing to clear the app's data from within the device settings UI. It
+ * erases all dynamic data associated with the app -- its private data and data in its
+ * private area on external storage -- but does not remove the installed application
+ * itself, nor any OBB files. It also revokes all runtime permissions that the app has acquired,
+ * clears all notifications and removes all Uri grants related to this application.
+ *
+ * @return {@code true} if the application successfully requested that the application's
+ * data be erased; {@code false} otherwise.
+ */
+ public boolean clearApplicationUserData() {
+ return clearApplicationUserData(mContext.getPackageName(), null);
+ }
+
+ /**
+ * Permits an application to get the persistent URI permissions granted to another.
+ *
+ * <p>Typically called by Settings or DocumentsUI, requires
+ * {@code GET_APP_GRANTED_URI_PERMISSIONS}.
+ *
+ * @param packageName application to look for the granted permissions, or {@code null} to get
+ * granted permissions for all applications
+ * @return list of granted URI permissions
+ *
+ * @hide
+ * @deprecated use {@link UriGrantsManager#getGrantedUriPermissions(String)} instead.
+ */
+ @Deprecated
+ public ParceledListSlice<GrantedUriPermission> getGrantedUriPermissions(
+ @Nullable String packageName) {
+ return ((UriGrantsManager) mContext.getSystemService(Context.URI_GRANTS_SERVICE))
+ .getGrantedUriPermissions(packageName);
+ }
+
+ /**
+ * Permits an application to clear the persistent URI permissions granted to another.
+ *
+ * <p>Typically called by Settings, requires {@code CLEAR_APP_GRANTED_URI_PERMISSIONS}.
+ *
+ * @param packageName application to clear its granted permissions
+ *
+ * @hide
+ * @deprecated use {@link UriGrantsManager#clearGrantedUriPermissions(String)} instead.
+ */
+ @Deprecated
+ public void clearGrantedUriPermissions(String packageName) {
+ ((UriGrantsManager) mContext.getSystemService(Context.URI_GRANTS_SERVICE))
+ .clearGrantedUriPermissions(packageName);
+ }
+
+ /**
+ * Information you can retrieve about any processes that are in an error condition.
+ */
+ public static class ProcessErrorStateInfo implements Parcelable {
+ /**
+ * Condition codes
+ */
+ public static final int NO_ERROR = 0;
+ public static final int CRASHED = 1;
+ public static final int NOT_RESPONDING = 2;
+
+ /**
+ * The condition that the process is in.
+ */
+ public int condition;
+
+ /**
+ * The process name in which the crash or error occurred.
+ */
+ public String processName;
+
+ /**
+ * The pid of this process; 0 if none
+ */
+ public int pid;
+
+ /**
+ * The kernel user-ID that has been assigned to this process;
+ * currently this is not a unique ID (multiple applications can have
+ * the same uid).
+ */
+ public int uid;
+
+ /**
+ * The activity name associated with the error, if known. May be null.
+ */
+ public String tag;
+
+ /**
+ * A short message describing the error condition.
+ */
+ public String shortMsg;
+
+ /**
+ * A long message describing the error condition.
+ */
+ public String longMsg;
+
+ /**
+ * The stack trace where the error originated. May be null.
+ */
+ public String stackTrace;
+
+ /**
+ * to be deprecated: This value will always be null.
+ */
+ public byte[] crashData = null;
+
+ public ProcessErrorStateInfo() {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(condition);
+ dest.writeString(processName);
+ dest.writeInt(pid);
+ dest.writeInt(uid);
+ dest.writeString(tag);
+ dest.writeString(shortMsg);
+ dest.writeString(longMsg);
+ dest.writeString(stackTrace);
+ }
+
+ public void readFromParcel(Parcel source) {
+ condition = source.readInt();
+ processName = source.readString();
+ pid = source.readInt();
+ uid = source.readInt();
+ tag = source.readString();
+ shortMsg = source.readString();
+ longMsg = source.readString();
+ stackTrace = source.readString();
+ }
+
+ public static final @android.annotation.NonNull Creator<ProcessErrorStateInfo> CREATOR =
+ new Creator<ProcessErrorStateInfo>() {
+ public ProcessErrorStateInfo createFromParcel(Parcel source) {
+ return new ProcessErrorStateInfo(source);
+ }
+ public ProcessErrorStateInfo[] newArray(int size) {
+ return new ProcessErrorStateInfo[size];
+ }
+ };
+
+ private ProcessErrorStateInfo(Parcel source) {
+ readFromParcel(source);
+ }
+ }
+
+ /**
+ * Returns a list of any processes that are currently in an error condition. The result
+ * will be null if all processes are running properly at this time.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#TIRAMISU Android TIRAMISU}, for regular apps
+ * this method will only return {@link ProcessErrorStateInfo} records for the processes running
+ * as the caller's uid, unless the caller has the permission
+ * {@link android.Manifest.permission#DUMP}.
+ * </p>
+ *
+ * @return Returns a list of {@link ProcessErrorStateInfo} records, or null if there are no
+ * current error conditions (it will not return an empty list). This list ordering is not
+ * specified.
+ */
+ public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+ try {
+ return getService().getProcessesInErrorState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Information you can retrieve about a running process.
+ */
+ public static class RunningAppProcessInfo implements Parcelable {
+ /**
+ * The name of the process that this object is associated with
+ */
+ public String processName;
+
+ /**
+ * The pid of this process; 0 if none
+ */
+ public int pid;
+
+ /**
+ * The user id of this process.
+ */
+ public int uid;
+
+ /**
+ * All packages that have been loaded into the process.
+ */
+ public String[] pkgList;
+
+ /**
+ * Additional packages loaded into the process as dependency.
+ * @hide
+ */
+ public String[] pkgDeps;
+
+ /**
+ * Constant for {@link #flags}: this is an app that is unable to
+ * correctly save its state when going to the background,
+ * so it can not be killed while in the background.
+ * @hide
+ */
+ public static final int FLAG_CANT_SAVE_STATE = 1<<0;
+
+ /**
+ * Constant for {@link #flags}: this process is associated with a
+ * persistent system app.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int FLAG_PERSISTENT = 1<<1;
+
+ /**
+ * Constant for {@link #flags}: this process is associated with a
+ * persistent system app.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static final int FLAG_HAS_ACTIVITIES = 1<<2;
+
+ /**
+ * Flags of information. May be any of
+ * {@link #FLAG_CANT_SAVE_STATE}.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int flags;
+
+ /**
+ * Last memory trim level reported to the process: corresponds to
+ * the values supplied to {@link android.content.ComponentCallbacks2#onTrimMemory(int)
+ * ComponentCallbacks2.onTrimMemory(int)}.
+ */
+ public int lastTrimLevel;
+
+ /** @hide */
+ @IntDef(prefix = { "IMPORTANCE_" }, value = {
+ IMPORTANCE_FOREGROUND,
+ IMPORTANCE_FOREGROUND_SERVICE,
+ IMPORTANCE_TOP_SLEEPING,
+ IMPORTANCE_VISIBLE,
+ IMPORTANCE_PERCEPTIBLE,
+ IMPORTANCE_CANT_SAVE_STATE,
+ IMPORTANCE_SERVICE,
+ IMPORTANCE_CACHED,
+ IMPORTANCE_GONE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Importance {}
+
+ /**
+ * Constant for {@link #importance}: This process is running the
+ * foreground UI; that is, it is the thing currently at the top of the screen
+ * that the user is interacting with.
+ */
+ public static final int IMPORTANCE_FOREGROUND = 100;
+
+ /**
+ * Constant for {@link #importance}: This process is running a foreground
+ * service, for example to perform music playback even while the user is
+ * not immediately in the app. This generally indicates that the process
+ * is doing something the user actively cares about.
+ */
+ public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
+
+ /**
+ * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of
+ * {@link #IMPORTANCE_TOP_SLEEPING}. As of Android
+ * {@link android.os.Build.VERSION_CODES#P}, this is considered much less
+ * important since we want to reduce what apps can do when the screen is off.
+ */
+ @Deprecated
+ public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
+
+ /**
+ * Constant for {@link #importance}: This process is running something
+ * that is actively visible to the user, though not in the immediate
+ * foreground. This may be running a window that is behind the current
+ * foreground (so paused and with its state saved, not interacting with
+ * the user, but visible to them to some degree); it may also be running
+ * other services under the system's control that it inconsiders important.
+ */
+ public static final int IMPORTANCE_VISIBLE = 200;
+
+ /**
+ * Constant for {@link #importance}: {@link #IMPORTANCE_PERCEPTIBLE} had this wrong value
+ * before {@link Build.VERSION_CODES#O}. Since the {@link Build.VERSION_CODES#O} SDK,
+ * the value of {@link #IMPORTANCE_PERCEPTIBLE} has been fixed.
+ *
+ * <p>The system will return this value instead of {@link #IMPORTANCE_PERCEPTIBLE}
+ * on Android versions below {@link Build.VERSION_CODES#O}.
+ *
+ * <p>On Android version {@link Build.VERSION_CODES#O} and later, this value will still be
+ * returned for apps with the target API level below {@link Build.VERSION_CODES#O}.
+ * For apps targeting version {@link Build.VERSION_CODES#O} and later,
+ * the correct value {@link #IMPORTANCE_PERCEPTIBLE} will be returned.
+ */
+ public static final int IMPORTANCE_PERCEPTIBLE_PRE_26 = 130;
+
+ /**
+ * Constant for {@link #importance}: This process is not something the user
+ * is directly aware of, but is otherwise perceptible to them to some degree.
+ */
+ public static final int IMPORTANCE_PERCEPTIBLE = 230;
+
+ /**
+ * Constant for {@link #importance}: {@link #IMPORTANCE_CANT_SAVE_STATE} had
+ * this wrong value
+ * before {@link Build.VERSION_CODES#O}. Since the {@link Build.VERSION_CODES#O} SDK,
+ * the value of {@link #IMPORTANCE_CANT_SAVE_STATE} has been fixed.
+ *
+ * <p>The system will return this value instead of {@link #IMPORTANCE_CANT_SAVE_STATE}
+ * on Android versions below {@link Build.VERSION_CODES#O}.
+ *
+ * <p>On Android version {@link Build.VERSION_CODES#O} after, this value will still be
+ * returned for apps with the target API level below {@link Build.VERSION_CODES#O}.
+ * For apps targeting version {@link Build.VERSION_CODES#O} and later,
+ * the correct value {@link #IMPORTANCE_CANT_SAVE_STATE} will be returned.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @TestApi
+ public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
+
+ /**
+ * Constant for {@link #importance}: This process contains services
+ * that should remain running. These are background services apps have
+ * started, not something the user is aware of, so they may be killed by
+ * the system relatively freely (though it is generally desired that they
+ * stay running as long as they want to).
+ */
+ public static final int IMPORTANCE_SERVICE = 300;
+
+ /**
+ * Constant for {@link #importance}: This process is running the foreground
+ * UI, but the device is asleep so it is not visible to the user. Though the
+ * system will try hard to keep its process from being killed, in all other
+ * ways we consider it a kind of cached process, with the limitations that go
+ * along with that state: network access, running background services, etc.
+ */
+ public static final int IMPORTANCE_TOP_SLEEPING = 325;
+
+ /**
+ * Constant for {@link #importance}: This process is running an
+ * application that can not save its state, and thus can't be killed
+ * while in the background. This will be used with apps that have
+ * {@link android.R.attr#cantSaveState} set on their application tag.
+ */
+ public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
+
+ /**
+ * Constant for {@link #importance}: This process process contains
+ * cached code that is expendable, not actively running any app components
+ * we care about.
+ */
+ public static final int IMPORTANCE_CACHED = 400;
+
+ /**
+ * @deprecated Renamed to {@link #IMPORTANCE_CACHED}.
+ */
+ public static final int IMPORTANCE_BACKGROUND = IMPORTANCE_CACHED;
+
+ /**
+ * Constant for {@link #importance}: This process is empty of any
+ * actively running code.
+ * @deprecated This value is no longer reported, use {@link #IMPORTANCE_CACHED} instead.
+ */
+ @Deprecated
+ public static final int IMPORTANCE_EMPTY = 500;
+
+ /**
+ * Constant for {@link #importance}: This process does not exist.
+ */
+ public static final int IMPORTANCE_GONE = 1000;
+
+ /**
+ * Convert a proc state to the correspondent IMPORTANCE_* constant. If the return value
+ * will be passed to a client, use {@link #procStateToImportanceForClient}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public static @Importance int procStateToImportance(int procState) {
+ if (procState == PROCESS_STATE_NONEXISTENT) {
+ return IMPORTANCE_GONE;
+ } else if (procState >= PROCESS_STATE_HOME) {
+ return IMPORTANCE_CACHED;
+ } else if (procState == PROCESS_STATE_HEAVY_WEIGHT) {
+ return IMPORTANCE_CANT_SAVE_STATE;
+ } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
+ return IMPORTANCE_TOP_SLEEPING;
+ } else if (procState >= PROCESS_STATE_SERVICE) {
+ return IMPORTANCE_SERVICE;
+ } else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ return IMPORTANCE_PERCEPTIBLE;
+ } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ return IMPORTANCE_VISIBLE;
+ } else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
+ return IMPORTANCE_FOREGROUND_SERVICE;
+ } else {
+ return IMPORTANCE_FOREGROUND;
+ }
+ }
+
+ /**
+ * Convert a proc state to the correspondent IMPORTANCE_* constant for a client represented
+ * by a given {@link Context}, with converting {@link #IMPORTANCE_PERCEPTIBLE}
+ * and {@link #IMPORTANCE_CANT_SAVE_STATE} to the corresponding "wrong" value if the
+ * client's target SDK < {@link VERSION_CODES#O}.
+ * @hide
+ */
+ public static @Importance int procStateToImportanceForClient(int procState,
+ Context clientContext) {
+ return procStateToImportanceForTargetSdk(procState,
+ clientContext.getApplicationInfo().targetSdkVersion);
+ }
+
+ /**
+ * See {@link #procStateToImportanceForClient}.
+ * @hide
+ */
+ public static @Importance int procStateToImportanceForTargetSdk(int procState,
+ int targetSdkVersion) {
+ final int importance = procStateToImportance(procState);
+
+ // For pre O apps, convert to the old, wrong values.
+ if (targetSdkVersion < VERSION_CODES.O) {
+ switch (importance) {
+ case IMPORTANCE_PERCEPTIBLE:
+ return IMPORTANCE_PERCEPTIBLE_PRE_26;
+ case IMPORTANCE_TOP_SLEEPING:
+ return IMPORTANCE_TOP_SLEEPING_PRE_28;
+ case IMPORTANCE_CANT_SAVE_STATE:
+ return IMPORTANCE_CANT_SAVE_STATE_PRE_26;
+ }
+ }
+ return importance;
+ }
+
+ /** @hide */
+ public static int importanceToProcState(@Importance int importance) {
+ if (importance == IMPORTANCE_GONE) {
+ return PROCESS_STATE_NONEXISTENT;
+ } else if (importance >= IMPORTANCE_CACHED) {
+ return PROCESS_STATE_HOME;
+ } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) {
+ return PROCESS_STATE_HEAVY_WEIGHT;
+ } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
+ return PROCESS_STATE_TOP_SLEEPING;
+ } else if (importance >= IMPORTANCE_SERVICE) {
+ return PROCESS_STATE_SERVICE;
+ } else if (importance >= IMPORTANCE_PERCEPTIBLE) {
+ return PROCESS_STATE_TRANSIENT_BACKGROUND;
+ } else if (importance >= IMPORTANCE_VISIBLE) {
+ return PROCESS_STATE_IMPORTANT_FOREGROUND;
+ } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
+ return PROCESS_STATE_IMPORTANT_FOREGROUND;
+ } else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
+ return PROCESS_STATE_FOREGROUND_SERVICE;
+ // TODO: Asymmetrical mapping for LOCATION service type. Ok?
+ } else {
+ return PROCESS_STATE_TOP;
+ }
+ }
+
+ /**
+ * The relative importance level that the system places on this process.
+ * These constants are numbered so that "more important" values are
+ * always smaller than "less important" values.
+ */
+ public @Importance int importance;
+
+ /**
+ * An additional ordering within a particular {@link #importance}
+ * category, providing finer-grained information about the relative
+ * utility of processes within a category. This number means nothing
+ * except that a smaller values are more recently used (and thus
+ * more important). Currently an LRU value is only maintained for
+ * the {@link #IMPORTANCE_CACHED} category, though others may
+ * be maintained in the future.
+ */
+ public int lru;
+
+ /**
+ * Constant for {@link #importanceReasonCode}: nothing special has
+ * been specified for the reason for this level.
+ */
+ public static final int REASON_UNKNOWN = 0;
+
+ /**
+ * Constant for {@link #importanceReasonCode}: one of the application's
+ * content providers is being used by another process. The pid of
+ * the client process is in {@link #importanceReasonPid} and the
+ * target provider in this process is in
+ * {@link #importanceReasonComponent}.
+ */
+ public static final int REASON_PROVIDER_IN_USE = 1;
+
+ /**
+ * Constant for {@link #importanceReasonCode}: one of the application's
+ * content providers is being used by another process. The pid of
+ * the client process is in {@link #importanceReasonPid} and the
+ * target provider in this process is in
+ * {@link #importanceReasonComponent}.
+ */
+ public static final int REASON_SERVICE_IN_USE = 2;
+
+ /**
+ * The reason for {@link #importance}, if any.
+ */
+ public int importanceReasonCode;
+
+ /**
+ * For the specified values of {@link #importanceReasonCode}, this
+ * is the process ID of the other process that is a client of this
+ * process. This will be 0 if no other process is using this one.
+ */
+ public int importanceReasonPid;
+
+ /**
+ * For the specified values of {@link #importanceReasonCode}, this
+ * is the name of the component that is being used in this process.
+ */
+ public ComponentName importanceReasonComponent;
+
+ /**
+ * When {@link #importanceReasonPid} is non-0, this is the importance
+ * of the other pid. @hide
+ */
+ public int importanceReasonImportance;
+
+ /**
+ * Current process state, as per PROCESS_STATE_* constants.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int processState;
+
+ /**
+ * Whether the app is focused in multi-window environment.
+ * @hide
+ */
+ public boolean isFocused;
+
+ /**
+ * Copy of {@link com.android.server.am.ProcessRecord#lastActivityTime} of the process.
+ * @hide
+ */
+ public long lastActivityTime;
+
+ public RunningAppProcessInfo() {
+ importance = IMPORTANCE_FOREGROUND;
+ importanceReasonCode = REASON_UNKNOWN;
+ processState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+ isFocused = false;
+ lastActivityTime = 0;
+ }
+
+ public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
+ processName = pProcessName;
+ pid = pPid;
+ pkgList = pArr;
+ isFocused = false;
+ lastActivityTime = 0;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(processName);
+ dest.writeInt(pid);
+ dest.writeInt(uid);
+ dest.writeStringArray(pkgList);
+ dest.writeStringArray(pkgDeps);
+ dest.writeInt(this.flags);
+ dest.writeInt(lastTrimLevel);
+ dest.writeInt(importance);
+ dest.writeInt(lru);
+ dest.writeInt(importanceReasonCode);
+ dest.writeInt(importanceReasonPid);
+ ComponentName.writeToParcel(importanceReasonComponent, dest);
+ dest.writeInt(importanceReasonImportance);
+ dest.writeInt(processState);
+ dest.writeInt(isFocused ? 1 : 0);
+ dest.writeLong(lastActivityTime);
+ }
+
+ public void readFromParcel(Parcel source) {
+ processName = source.readString();
+ pid = source.readInt();
+ uid = source.readInt();
+ pkgList = source.readStringArray();
+ pkgDeps = source.readStringArray();
+ flags = source.readInt();
+ lastTrimLevel = source.readInt();
+ importance = source.readInt();
+ lru = source.readInt();
+ importanceReasonCode = source.readInt();
+ importanceReasonPid = source.readInt();
+ importanceReasonComponent = ComponentName.readFromParcel(source);
+ importanceReasonImportance = source.readInt();
+ processState = source.readInt();
+ isFocused = source.readInt() != 0;
+ lastActivityTime = source.readLong();
+ }
+
+ public static final @android.annotation.NonNull Creator<RunningAppProcessInfo> CREATOR =
+ new Creator<RunningAppProcessInfo>() {
+ public RunningAppProcessInfo createFromParcel(Parcel source) {
+ return new RunningAppProcessInfo(source);
+ }
+ public RunningAppProcessInfo[] newArray(int size) {
+ return new RunningAppProcessInfo[size];
+ }
+ };
+
+ private RunningAppProcessInfo(Parcel source) {
+ readFromParcel(source);
+ }
+ }
+
+ /**
+ * Returns a list of application processes installed on external media
+ * that are running on the device.
+ *
+ * <p><b>Note: this method is only intended for debugging or building
+ * a user-facing process management UI.</b></p>
+ *
+ * @return Returns a list of ApplicationInfo records, or null if none
+ * This list ordering is not specified.
+ * @hide
+ */
+ public List<ApplicationInfo> getRunningExternalApplications() {
+ try {
+ return getService().getRunningExternalApplications();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Query whether the user has enabled background restrictions for this app.
+ *
+ * <p> The user may chose to do this, if they see that an app is consuming an unreasonable
+ * amount of battery while in the background. </p>
+ *
+ * <p> If true, any work that the app tries to do will be aggressively restricted while it is in
+ * the background. At a minimum, jobs and alarms will not execute and foreground services
+ * cannot be started unless an app activity is in the foreground. </p>
+ *
+ * <p><b> Note that these restrictions stay in effect even when the device is charging.</b></p>
+ *
+ * @return true if user has enforced background restrictions for this app, false otherwise.
+ */
+ public boolean isBackgroundRestricted() {
+ try {
+ return getService().isBackgroundRestricted(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the memory trim mode for a process and schedules a memory trim operation.
+ *
+ * <p><b>Note: this method is only intended for testing framework.</b></p>
+ *
+ * @return Returns true if successful.
+ * @hide
+ */
+ public boolean setProcessMemoryTrimLevel(String process, int userId, int level) {
+ try {
+ return getService().setProcessMemoryTrimLevel(process, userId,
+ level);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a list of application processes that are running on the device.
+ *
+ * <p><b>Note: this method is only intended for debugging or building
+ * a user-facing process management UI.</b></p>
+ *
+ * @return Returns a list of RunningAppProcessInfo records, or null if there are no
+ * running processes (it will not return an empty list). This list ordering is not
+ * specified.
+ */
+ public List<RunningAppProcessInfo> getRunningAppProcesses() {
+ try {
+ return getService().getRunningAppProcesses();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of {@link ApplicationStartInfo} records containing the information about the
+ * most recent app startups.
+ *
+ * <p class="note"> Note: System stores this historical information in a ring buffer and only
+ * the most recent records will be returned. </p>
+ *
+ * @param maxNum The maximum number of results to be returned; a value of 0
+ * means to ignore this parameter and return all matching records. If fewer
+ * records exist, all existing records will be returned.
+ *
+ * @return a list of {@link ApplicationStartInfo} records matching the criteria, sorted in
+ * the order from most recent to least recent.
+ *
+ * @hide
+ */
+ @NonNull
+ public List<ApplicationStartInfo> getHistoricalProcessStartReasons(
+ @IntRange(from = 0) int maxNum) {
+ try {
+ ParceledListSlice<ApplicationStartInfo> startInfos = getService()
+ .getHistoricalProcessStartReasons(null, maxNum, mContext.getUserId());
+ return startInfos == null ? Collections.emptyList() : startInfos.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of {@link ApplicationStartInfo} records containing the information about the
+ * most recent app startups.
+ *
+ * <p class="note"> Note: System stores this historical information in a ring buffer and only
+ * the most recent records will be returned. </p>
+ *
+ * @param packageName Package name for which app startups to receive.
+ * @param maxNum The maximum number of results to be returned; a value of 0
+ * means to ignore this parameter and return all matching records. If fewer
+ * records exist, all existing records will be returned.
+ *
+ * @return a list of {@link ApplicationStartInfo} records matching the criteria, sorted in
+ * the order from most recent to least recent.
+ *
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.DUMP)
+ public List<ApplicationStartInfo> getExternalHistoricalProcessStartReasons(
+ @NonNull String packageName, @IntRange(from = 0) int maxNum) {
+ try {
+ ParceledListSlice<ApplicationStartInfo> startInfos = getService()
+ .getHistoricalProcessStartReasons(packageName, maxNum, mContext.getUserId());
+ return startInfos == null ? Collections.emptyList() : startInfos.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to receive {@link ApplicationStartInfo} object once recording of startup related
+ * metrics is complete.
+ * Use with {@link #setApplicationStartInfoCompleteListener}.
+ *
+ * @hide
+ */
+ public interface ApplicationStartInfoCompleteListener {
+ /** {@link ApplicationStartInfo} is complete, no more info will be added. */
+ void onApplicationStartInfoComplete(@NonNull ApplicationStartInfo applicationStartInfo);
+ }
+
+ /**
+ * Sets a callback to be notified when the {@link ApplicationStartInfo} records of this startup
+ * are complete.
+ *
+ * <p class="note"> Note: callback will not wait for {@link Activity#reportFullyDrawn} to occur.
+ * Timestamp for fully drawn may be added after callback occurs. Set callback after invoking
+ * {@link Activity#reportFullyDrawn} if timestamp for fully drawn is required.</p>
+ *
+ * <p class="note"> Note: if start records have already been retrieved, the callback will be
+ * invoked immediately on the specified executor with the previously resolved AppStartInfo.</p>
+ *
+ * <p class="note"> Note: callback is asynchronous and should be made from a background thread.
+ * </p>
+ *
+ * @param executor The executor on which the listener should be called.
+ * @param listener Callback to be called when collection of {@link ApplicationStartInfo} is
+ * complete. Will replace existing listener if one is already attached.
+ *
+ * @throws IllegalArgumentException if executor or listener are null.
+ *
+ * @hide
+ */
+ public void setApplicationStartInfoCompleteListener(@NonNull final Executor executor,
+ @NonNull final ApplicationStartInfoCompleteListener listener) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ IApplicationStartInfoCompleteListener callback =
+ new IApplicationStartInfoCompleteListener.Stub() {
+ @Override
+ public void onApplicationStartInfoComplete(ApplicationStartInfo applicationStartInfo) {
+ executor.execute(() ->
+ listener.onApplicationStartInfoComplete(applicationStartInfo));
+ }
+ };
+ try {
+ getService().setApplicationStartInfoCompleteListener(callback, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the callback set by {@link #setApplicationStartInfoCompleteListener} if there is one.
+ *
+ * @hide
+ */
+ public void removeApplicationStartInfoCompleteListener() {
+ try {
+ getService().removeApplicationStartInfoCompleteListener(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of {@link ApplicationExitInfo} records containing the reasons for the most
+ * recent app deaths.
+ *
+ * <p class="note"> Note: System stores this historical information in a ring buffer and only
+ * the most recent records will be returned. </p>
+ *
+ * <p class="note"> Note: In the case that this application was bound to an external service
+ * with flag {@link android.content.Context#BIND_EXTERNAL_SERVICE}, the process of that external
+ * service will be included in this package's exit info. </p>
+ *
+ * @param packageName Optional, a null value means match all packages belonging to the
+ * caller's UID. If this package belongs to another UID, you must hold
+ * {@link android.Manifest.permission#DUMP} in order to retrieve it.
+ * @param pid A process ID that used to belong to this package but died later; a value
+ * of 0 means to ignore this parameter and return all matching records.
+ * @param maxNum The maximum number of results to be returned; a value of 0
+ * means to ignore this parameter and return all matching records
+ *
+ * @return a list of {@link ApplicationExitInfo} records matching the criteria, sorted in
+ * the order from most recent to least recent.
+ */
+ @NonNull
+ public List<ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String packageName,
+ @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum) {
+ try {
+ ParceledListSlice<ApplicationExitInfo> r = getService().getHistoricalProcessExitReasons(
+ packageName, pid, maxNum, mContext.getUserId());
+ return r == null ? Collections.emptyList() : r.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set custom state data for this process. It will be included in the record of
+ * {@link ApplicationExitInfo} on the death of the current calling process; the new process
+ * of the app can retrieve this state data by calling
+ * {@link android.app.ApplicationExitInfo#getProcessStateSummary()
+ * ApplicationExitInfo.getProcessStateSummary()} on the record returned by
+ * {@link #getHistoricalProcessExitReasons}.
+ *
+ * <p> This would be useful for the calling app to save its stateful data: if it's
+ * killed later for any reason, the new process of the app can know what the
+ * previous process of the app was doing. For instance, you could use this to encode
+ * the current level in a game, or a set of features/experiments that were enabled. Later you
+ * could analyze under what circumstances the app tends to crash or use too much memory.
+ * However, it's not suggested to rely on this to restore the applications previous UI state
+ * or so, it's only meant for analyzing application healthy status.</p>
+ *
+ * <p> System might decide to throttle the calls to this API; so call this API in a reasonable
+ * manner, excessive calls to this API could result a {@link java.lang.RuntimeException}.
+ * </p>
+ *
+ * @param state The state data. To be advised, <b>DO NOT</b> include sensitive information/data
+ * (PII, SPII, or other sensitive user data) here. Maximum length is 128 bytes.
+ */
+ public void setProcessStateSummary(@Nullable byte[] state) {
+ try {
+ getService().setProcessStateSummary(state);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return Whether or not the low memory kill will be reported in
+ * {@link #getHistoricalProcessExitReasons}.
+ *
+ * @see ApplicationExitInfo#REASON_LOW_MEMORY
+ */
+ public static boolean isLowMemoryKillReportSupported() {
+ return SystemProperties.getBoolean("persist.sys.lmk.reportkills", false);
+ }
+
+ /**
+ * Returns the process state of this uid.
+ *
+ * If the caller does not hold {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission, they can only query process state of UIDs running in the same user as the caller.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.PACKAGE_USAGE_STATS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ }, conditional = true)
+ public int getUidProcessState(int uid) {
+ try {
+ return getService().getUidProcessState(uid, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the process capability of this uid.
+ *
+ * If the caller does not hold {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission, they can only query process capabilities of UIDs running in the same user
+ * as the caller.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.PACKAGE_USAGE_STATS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ }, conditional = true)
+ public @ProcessCapability int getUidProcessCapabilities(int uid) {
+ try {
+ return getService().getUidProcessCapabilities(uid, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the importance of a given package name, based on the processes that are
+ * currently running. The return value is one of the importance constants defined
+ * in {@link RunningAppProcessInfo}, giving you the highest importance of all the
+ * processes that this package has code running inside of. If there are no processes
+ * running its code, {@link RunningAppProcessInfo#IMPORTANCE_GONE} is returned.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ public @RunningAppProcessInfo.Importance int getPackageImportance(String packageName) {
+ try {
+ int procState = getService().getPackageProcessState(packageName,
+ mContext.getOpPackageName());
+ return RunningAppProcessInfo.procStateToImportanceForClient(procState, mContext);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the importance of a given uid, based on the processes that are
+ * currently running. The return value is one of the importance constants defined
+ * in {@link RunningAppProcessInfo}, giving you the highest importance of all the
+ * processes that this uid has running. If there are no processes
+ * running its code, {@link RunningAppProcessInfo#IMPORTANCE_GONE} is returned.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ public @RunningAppProcessInfo.Importance int getUidImportance(int uid) {
+ try {
+ int procState = getService().getUidProcessState(uid,
+ mContext.getOpPackageName());
+ return RunningAppProcessInfo.procStateToImportanceForClient(procState, mContext);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to get reports about changes to the importance of a uid. Use with
+ * {@link #addOnUidImportanceListener}.
+ * @hide
+ */
+ @SystemApi
+ public interface OnUidImportanceListener {
+ /**
+ * The importance if a given uid has changed. Will be one of the importance
+ * values in {@link RunningAppProcessInfo};
+ * {@link RunningAppProcessInfo#IMPORTANCE_GONE IMPORTANCE_GONE} will be reported
+ * when the uid is no longer running at all. This callback will happen on a thread
+ * from a thread pool, not the main UI thread.
+ * @param uid The uid whose importance has changed.
+ * @param importance The new importance value as per {@link RunningAppProcessInfo}.
+ */
+ void onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance);
+ }
+
+ /**
+ * Start monitoring changes to the imoportance of uids running in the system.
+ * @param listener The listener callback that will receive change reports.
+ * @param importanceCutpoint The level of importance in which the caller is interested
+ * in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
+ * is used here, you will receive a call each time a uids importance transitions between
+ * being <= {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} and
+ * > {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.
+ *
+ * <p>The caller must hold the {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
+ * permission to use this feature.</p>
+ *
+ * @throws IllegalArgumentException If the listener is already registered.
+ * @throws SecurityException If the caller does not hold
+ * {@link android.Manifest.permission#PACKAGE_USAGE_STATS}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ public void addOnUidImportanceListener(OnUidImportanceListener listener,
+ @RunningAppProcessInfo.Importance int importanceCutpoint) {
+ synchronized (this) {
+ if (mImportanceListeners.containsKey(listener)) {
+ throw new IllegalArgumentException("Listener already registered: " + listener);
+ }
+ // TODO: implement the cut point in the system process to avoid IPCs.
+ MyUidObserver observer = new MyUidObserver(listener, mContext);
+ try {
+ getService().registerUidObserver(observer,
+ UID_OBSERVER_PROCSTATE | UID_OBSERVER_GONE,
+ RunningAppProcessInfo.importanceToProcState(importanceCutpoint),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mImportanceListeners.put(listener, observer);
+ }
+ }
+
+ /**
+ * Remove an importance listener that was previously registered with
+ * {@link #addOnUidImportanceListener}.
+ *
+ * @throws IllegalArgumentException If the listener is not registered.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ public void removeOnUidImportanceListener(OnUidImportanceListener listener) {
+ synchronized (this) {
+ MyUidObserver observer = mImportanceListeners.remove(listener);
+ if (observer == null) {
+ throw new IllegalArgumentException("Listener not registered: " + listener);
+ }
+ try {
+ getService().unregisterUidObserver(observer);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Return global memory state information for the calling process. This
+ * does not fill in all fields of the {@link RunningAppProcessInfo}. The
+ * only fields that will be filled in are
+ * {@link RunningAppProcessInfo#pid},
+ * {@link RunningAppProcessInfo#uid},
+ * {@link RunningAppProcessInfo#lastTrimLevel},
+ * {@link RunningAppProcessInfo#importance},
+ * {@link RunningAppProcessInfo#lru}, and
+ * {@link RunningAppProcessInfo#importanceReasonCode}.
+ */
+ static public void getMyMemoryState(RunningAppProcessInfo outState) {
+ try {
+ getService().getMyMemoryState(outState);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return information about the memory usage of one or more processes.
+ *
+ * <p><b>Note: this method is only intended for debugging or building
+ * a user-facing process management UI.</b></p>
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#Q Android Q}, for regular apps this method
+ * will only return information about the memory info for the processes running as the
+ * caller's uid; no other process memory info is available and will be zero.
+ * Also of {@link android.os.Build.VERSION_CODES#Q Android Q} the sample rate allowed
+ * by this API is significantly limited, if called faster the limit you will receive the
+ * same data as the previous call.</p>
+ *
+ * @param pids The pids of the processes whose memory usage is to be
+ * retrieved.
+ * @return Returns an array of memory information, one for each
+ * requested pid.
+ */
+ public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) {
+ try {
+ return getService().getProcessMemoryInfo(pids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @deprecated This is now just a wrapper for
+ * {@link #killBackgroundProcesses(String)}; the previous behavior here
+ * is no longer available to applications because it allows them to
+ * break other applications by removing their alarms, stopping their
+ * services, etc.
+ */
+ @Deprecated
+ public void restartPackage(String packageName) {
+ killBackgroundProcesses(packageName);
+ }
+
+ /**
+ * Have the system immediately kill all background processes associated
+ * with the given package. This is the same as the kernel killing those
+ * processes to reclaim memory; the system will take care of restarting
+ * these processes in the future as needed.
+ *
+ * <p class="note">On devices that run Android 14 or higher,
+ * third party applications can only use this API to kill their own processes.
+ * </p>
+ *
+ * @param packageName The name of the package whose processes are to
+ * be killed.
+ */
+ @RequiresPermission(Manifest.permission.KILL_BACKGROUND_PROCESSES)
+ public void killBackgroundProcesses(String packageName) {
+ try {
+ getService().killBackgroundProcesses(packageName,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Kills the specified UID.
+ * @param uid The UID to kill.
+ * @param reason The reason for the kill.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.KILL_UID)
+ public void killUid(int uid, String reason) {
+ try {
+ getService().killUid(UserHandle.getAppId(uid),
+ UserHandle.getUserId(uid), reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Have the system perform a force stop of everything associated with
+ * the given application package. All processes that share its uid
+ * will be killed, all services it has running stopped, all activities
+ * removed, etc. In addition, a {@link Intent#ACTION_PACKAGE_RESTARTED}
+ * broadcast will be sent, so that any of its registered alarms can
+ * be stopped, notifications removed, etc.
+ *
+ * <p>You must hold the permission
+ * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to
+ * call this method.
+ *
+ * @param packageName The name of the package to be stopped.
+ * @param userId The user for which the running package is to be stopped.
+ *
+ * @hide This is not available to third party applications due to
+ * it allowing them to break other applications by stopping their
+ * services, removing their alarms, etc.
+ */
+ @UnsupportedAppUsage
+ public void forceStopPackageAsUser(String packageName, int userId) {
+ try {
+ getService().forceStopPackage(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see #forceStopPackageAsUser(String, int)
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
+ public void forceStopPackage(String packageName) {
+ forceStopPackageAsUser(packageName, mContext.getUserId());
+ }
+
+ /**
+ * Similar to {@link #forceStopPackageAsUser(String, int)} but will also stop the package even
+ * when the user is in the stopping state.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
+ public void forceStopPackageAsUserEvenWhenStopping(String packageName, @UserIdInt int userId) {
+ try {
+ getService().forceStopPackageEvenWhenStopping(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the current locales of the device. Calling app must have the permission
+ * {@code android.permission.CHANGE_CONFIGURATION} and
+ * {@code android.permission.WRITE_SETTINGS}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setDeviceLocales(@NonNull LocaleList locales) {
+ LocalePicker.updateLocales(locales);
+ }
+
+ /**
+ * Returns a list of supported locales by this system. It includes all locales that are
+ * selectable by the user, potentially including locales that the framework does not have
+ * translated resources for. To get locales that the framework has translated resources for, use
+ * {@code Resources.getSystem().getAssets().getLocales()} instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Collection<Locale> getSupportedLocales() {
+ ArrayList<Locale> locales = new ArrayList<>();
+ for (String localeTag : LocalePicker.getSupportedLocales(mContext)) {
+ locales.add(Locale.forLanguageTag(localeTag));
+ }
+ return locales;
+ }
+
+ /**
+ * Get the device configuration attributes.
+ */
+ public ConfigurationInfo getDeviceConfigurationInfo() {
+ try {
+ return getTaskService().getDeviceConfigurationInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the preferred density of icons for the launcher. This is used when
+ * custom drawables are created (e.g., for shortcuts).
+ *
+ * @return density in terms of DPI
+ */
+ public int getLauncherLargeIconDensity() {
+ final Resources res = mContext.getResources();
+ final int density = res.getDisplayMetrics().densityDpi;
+ final int sw = res.getConfiguration().smallestScreenWidthDp;
+
+ if (sw < 600) {
+ // Smaller than approx 7" tablets, use the regular icon size.
+ return density;
+ }
+
+ switch (density) {
+ case DisplayMetrics.DENSITY_LOW:
+ return DisplayMetrics.DENSITY_MEDIUM;
+ case DisplayMetrics.DENSITY_MEDIUM:
+ return DisplayMetrics.DENSITY_HIGH;
+ case DisplayMetrics.DENSITY_TV:
+ return DisplayMetrics.DENSITY_XHIGH;
+ case DisplayMetrics.DENSITY_HIGH:
+ return DisplayMetrics.DENSITY_XHIGH;
+ case DisplayMetrics.DENSITY_XHIGH:
+ return DisplayMetrics.DENSITY_XXHIGH;
+ case DisplayMetrics.DENSITY_XXHIGH:
+ return DisplayMetrics.DENSITY_XHIGH * 2;
+ default:
+ // The density is some abnormal value. Return some other
+ // abnormal value that is a reasonable scaling of it.
+ return (int)((density*1.5f)+.5f);
+ }
+ }
+
+ /**
+ * Get the preferred launcher icon size. This is used when custom drawables
+ * are created (e.g., for shortcuts).
+ *
+ * @return dimensions of square icons in terms of pixels
+ */
+ public int getLauncherLargeIconSize() {
+ return getLauncherLargeIconSizeInner(mContext);
+ }
+
+ static int getLauncherLargeIconSizeInner(Context context) {
+ final Resources res = context.getResources();
+ final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
+ final int sw = res.getConfiguration().smallestScreenWidthDp;
+
+ if (sw < 600) {
+ // Smaller than approx 7" tablets, use the regular icon size.
+ return size;
+ }
+
+ final int density = res.getDisplayMetrics().densityDpi;
+
+ switch (density) {
+ case DisplayMetrics.DENSITY_LOW:
+ return (size * DisplayMetrics.DENSITY_MEDIUM) / DisplayMetrics.DENSITY_LOW;
+ case DisplayMetrics.DENSITY_MEDIUM:
+ return (size * DisplayMetrics.DENSITY_HIGH) / DisplayMetrics.DENSITY_MEDIUM;
+ case DisplayMetrics.DENSITY_TV:
+ return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH;
+ case DisplayMetrics.DENSITY_HIGH:
+ return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH;
+ case DisplayMetrics.DENSITY_XHIGH:
+ return (size * DisplayMetrics.DENSITY_XXHIGH) / DisplayMetrics.DENSITY_XHIGH;
+ case DisplayMetrics.DENSITY_XXHIGH:
+ return (size * DisplayMetrics.DENSITY_XHIGH*2) / DisplayMetrics.DENSITY_XXHIGH;
+ default:
+ // The density is some abnormal value. Return some other
+ // abnormal value that is a reasonable scaling of it.
+ return (int)((size*1.5f) + .5f);
+ }
+ }
+
+ /**
+ * Returns "true" if the user interface is currently being messed with
+ * by a monkey.
+ */
+ public static boolean isUserAMonkey() {
+ try {
+ return getService().isUserAMonkey();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns "true" if device is running in a test harness.
+ *
+ * @deprecated this method is false for all user builds. Users looking to check if their device
+ * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
+ */
+ @Deprecated
+ public static boolean isRunningInTestHarness() {
+ return SystemProperties.getBoolean("ro.test_harness", false);
+ }
+
+ /**
+ * Returns "true" if the device is running in Test Harness Mode.
+ *
+ * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+ * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+ * want your app to behave differently when running in a test harness to skip setup screens that
+ * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+ * the first time it is launched.
+ *
+ * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+ * an instrumentation test, as it is not set for a standard device running a test.
+ */
+ public static boolean isRunningInUserTestHarness() {
+ return SystemProperties.getBoolean("persist.sys.test_harness", false);
+ }
+
+ /**
+ * Unsupported compiled sdk warning should always be shown for the intput activity
+ * even in cases where the system would normally not show the warning. E.g. when running in a
+ * test harness.
+ *
+ * @param activity The component name of the activity to always show the warning for.
+ *
+ * @hide
+ */
+ @TestApi
+ public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
+ try {
+ getTaskService().alwaysShowUnsupportedCompileSdkWarning(activity);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the launch count of each installed package.
+ *
+ * @hide
+ */
+ /*public Map<String, Integer> getAllPackageLaunchCounts() {
+ try {
+ IUsageStats usageStatsService = IUsageStats.Stub.asInterface(
+ ServiceManager.getService("usagestats"));
+ if (usageStatsService == null) {
+ return new HashMap<String, Integer>();
+ }
+
+ UsageStats.PackageStats[] allPkgUsageStats = usageStatsService.getAllPkgUsageStats(
+ ActivityThread.currentPackageName());
+ if (allPkgUsageStats == null) {
+ return new HashMap<String, Integer>();
+ }
+
+ Map<String, Integer> launchCounts = new HashMap<String, Integer>();
+ for (UsageStats.PackageStats pkgUsageStats : allPkgUsageStats) {
+ launchCounts.put(pkgUsageStats.getPackageName(), pkgUsageStats.getLaunchCount());
+ }
+
+ return launchCounts;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not query launch counts", e);
+ return new HashMap<String, Integer>();
+ }
+ }*/
+
+ /** @hide
+ * Determines whether the given UID can access unexported components
+ * @param uid the calling UID
+ * @return true if the calling UID is ROOT or SYSTEM
+ */
+ public static boolean canAccessUnexportedComponents(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ return (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static int checkComponentPermission(String permission, int uid,
+ int owningUid, boolean exported) {
+ // Root, system server get to do everything.
+ final int appId = UserHandle.getAppId(uid);
+ if (canAccessUnexportedComponents(uid)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ // Isolated processes don't get any permissions.
+ if (UserHandle.isIsolated(uid)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ // If there is a uid that owns whatever is being accessed, it has
+ // blanket access to it regardless of the permissions it requires.
+ if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ // If the target is not exported, then nobody else can get to it.
+ if (!exported) {
+ /*
+ RuntimeException here = new RuntimeException("here");
+ here.fillInStackTrace();
+ Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
+ here);
+ */
+ return PackageManager.PERMISSION_DENIED;
+ }
+ if (permission == null) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ try {
+ return AppGlobals.getPackageManager()
+ .checkUidPermission(permission, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public static int checkUidPermission(String permission, int uid) {
+ try {
+ return AppGlobals.getPackageManager()
+ .checkUidPermission(permission, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Helper for dealing with incoming user arguments to system service calls.
+ * Takes care of checking permissions and converting USER_CURRENT to the
+ * actual current user.
+ *
+ * @param callingPid The pid of the incoming call, as per Binder.getCallingPid().
+ * @param callingUid The uid of the incoming call, as per Binder.getCallingUid().
+ * @param userId The user id argument supplied by the caller -- this is the user
+ * they want to run as.
+ * @param allowAll If true, we will allow USER_ALL. This means you must be prepared
+ * to get a USER_ALL returned and deal with it correctly. If false,
+ * an exception will be thrown if USER_ALL is supplied.
+ * @param requireFull If true, the caller must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} to be able to run as a
+ * different user than their current process; otherwise they must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+ * @param name Optional textual name of the incoming call; only for generating error messages.
+ * @param callerPackage Optional package name of caller; only for error messages.
+ *
+ * @return Returns the user ID that the call should run as. Will always be a concrete
+ * user number, unless <var>allowAll</var> is true in which case it could also be
+ * USER_ALL.
+ */
+ public static int handleIncomingUser(int callingPid, int callingUid, int userId,
+ boolean allowAll, boolean requireFull, String name, String callerPackage) {
+ if (UserHandle.getUserId(callingUid) == userId) {
+ return userId;
+ }
+ try {
+ return getService().handleIncomingUser(callingPid,
+ callingUid, userId, allowAll, requireFull, name, callerPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the userId of the current foreground user. Requires system permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.INTERACT_ACROSS_USERS_FULL"
+ })
+ public static int getCurrentUser() {
+ try {
+ return getService().getCurrentUserId();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @param userid the user's id. Zero indicates the default user.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public boolean switchUser(int userid) {
+ try {
+ return getService().switchUser(userid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether switching to provided user was successful.
+ *
+ * @param user the user to switch to.
+ *
+ * @throws IllegalArgumentException if the user is null.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean switchUser(@NonNull UserHandle user) {
+ Preconditions.checkArgument(user != null, "UserHandle cannot be null.");
+
+ return switchUser(user.getIdentifier());
+ }
+
+ /**
+ * Starts the given user in background and assign the user to the given display.
+ *
+ * <p>This method will allow the user to launch activities on that display, and it's typically
+ * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
+ * supported by calling {@link UserManager#isVisibleBackgroundUsersSupported()}).
+ *
+ * <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
+ * user before starting a new one, this method does not stop the previous user running in
+ * background in the display, and it will return {@code false} in this case. It's up to the
+ * caller to call {@link #stopUser(int, boolean)} before starting a new user.
+ *
+ * @param userId user to be started in the display. It will return {@code false} if the user is
+ * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
+ * does not exist.
+ *
+ * @param displayId id of the display.
+ *
+ * @return whether the operation succeeded. Notice that if the user was already started in such
+ * display before, it will return {@code false}.
+ *
+ * @throws UnsupportedOperationException if the device does not support background users on
+ * secondary displays.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public boolean startUserInBackgroundVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ throw new UnsupportedOperationException(
+ "device does not support users on secondary displays");
+ }
+ try {
+ return getService().startUserInBackgroundVisibleOnDisplay(userId, displayId,
+ /* unlockProgressListener= */ null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the id of displays that can be used by
+ * {@link #startUserInBackgroundOnSecondaryDisplay(int, int)}.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public int[] getDisplayIdsForStartingVisibleBackgroundUsers() {
+ try {
+ return getService().getDisplayIdsForStartingVisibleBackgroundUsers();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the message that is shown when a user is switched from.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ public @Nullable String getSwitchingFromUserMessage() {
+ try {
+ return getService().getSwitchingFromUserMessage();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the message that is shown when a user is switched to.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ public @Nullable String getSwitchingToUserMessage() {
+ try {
+ return getService().getSwitchingToUserMessage();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Uses the value defined by the platform.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int STOP_USER_ON_SWITCH_DEFAULT = -1;
+
+ /**
+ * Overrides value defined by the platform and stop user on switch.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int STOP_USER_ON_SWITCH_TRUE = 1;
+
+ /**
+ * Overrides value defined by the platform and don't stop user on switch.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int STOP_USER_ON_SWITCH_FALSE = 0;
+
+ /** @hide */
+ @IntDef(prefix = { "STOP_USER_ON_SWITCH_" }, value = {
+ STOP_USER_ON_SWITCH_DEFAULT,
+ STOP_USER_ON_SWITCH_TRUE,
+ STOP_USER_ON_SWITCH_FALSE
+ })
+ public @interface StopUserOnSwitch {}
+
+ /**
+ * Sets whether the current foreground user (and its profiles) should be stopped after switched
+ * out.
+ *
+ * <p>Should only be used on tests. Doesn't apply to {@link UserHandle#SYSTEM system user}.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
+ try {
+ getService().setStopUserOnSwitch(value);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts a profile.
+ * To be used with non-managed profiles, managed profiles should use
+ * {@link UserManager#requestQuietModeEnabled}
+ *
+ * @param userHandle user handle of the profile.
+ * @return true if the profile has been successfully started or if the profile is already
+ * running, false if profile failed to start.
+ * @throws IllegalArgumentException if {@code userHandle} is not a profile.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ public boolean startProfile(@NonNull UserHandle userHandle) {
+ try {
+ return getService().startProfile(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stops a running profile.
+ * To be used with non-managed profiles, managed profiles should use
+ * {@link UserManager#requestQuietModeEnabled}
+ *
+ * @param userHandle user handle of the profile.
+ * @return true if the profile has been successfully stopped or is already stopped. Otherwise
+ * the exceptions listed below are thrown.
+ * @throws IllegalArgumentException if {@code userHandle} is not a profile.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
+ public boolean stopProfile(@NonNull UserHandle userHandle) {
+ try {
+ return getService().stopProfile(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates the MCC (Mobile Country Code) and MNC (Mobile Network Code) in the
+ * system configuration.
+ *
+ * @param mcc The new MCC.
+ * @param mnc The new MNC.
+ * @throws RemoteException; IllegalArgumentException if mcc or mnc is null;
+ * @return Returns {@code true} if the configuration was updated successfully;
+ * {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION)
+ public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) {
+ if (mcc == null || mnc == null) {
+ throw new IllegalArgumentException("mcc or mnc cannot be null.");
+ }
+ try {
+ return getService().updateMccMncConfiguration(mcc, mnc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stops the given {@code userId}.
+ *
+ * <p><b>NOTE:</b> on systems that support
+ * {@link UserManager#isVisibleBackgroundUsersSupported() background users on secondary
+ * displays}, this method will also unassign the user from the display it was started on.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public boolean stopUser(@UserIdInt int userId, boolean force) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ return false;
+ }
+ try {
+ return USER_OP_SUCCESS == getService().stopUser(
+ userId, force, /* callback= */ null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public static final int FLAG_OR_STOPPED = 1 << 0;
+ /** {@hide} */
+ public static final int FLAG_AND_LOCKED = 1 << 1;
+ /** {@hide} */
+ public static final int FLAG_AND_UNLOCKED = 1 << 2;
+ /** {@hide} */
+ public static final int FLAG_AND_UNLOCKING_OR_UNLOCKED = 1 << 3;
+
+ /**
+ * 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.
+ * @param userId the user's id. Zero indicates the default user.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isUserRunning(int userId) {
+ try {
+ return getService().isUserRunning(userId, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public boolean isVrModePackageEnabled(ComponentName component) {
+ try {
+ return getService().isVrModePackageEnabled(component);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Perform a system dump of various state associated with the given application
+ * package name. This call blocks while the dump is being performed, so should
+ * not be done on a UI thread. The data will be written to the given file
+ * descriptor as text.
+ * @param fd The file descriptor that the dump should be written to. The file
+ * descriptor is <em>not</em> closed by this function; the caller continues to
+ * own it.
+ * @param packageName The name of the package that is to be dumped.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public void dumpPackageState(FileDescriptor fd, String packageName) {
+ dumpPackageStateStatic(fd, packageName);
+ }
+
+ /**
+ * @hide
+ */
+ public static void dumpPackageStateStatic(FileDescriptor fd, String packageName) {
+ FileOutputStream fout = new FileOutputStream(fd);
+ PrintWriter pw = new FastPrintWriter(fout);
+ dumpService(pw, fd, "package", new String[] { packageName });
+ pw.println();
+ dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] {
+ "-a", "package", packageName });
+ pw.println();
+ dumpService(pw, fd, "meminfo", new String[] { "--local", "--package", packageName });
+ pw.println();
+ dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName });
+ pw.println();
+ dumpService(pw, fd, "usagestats", new String[] { packageName });
+ pw.println();
+ dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName });
+ pw.flush();
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isSystemReady() {
+ if (!sSystemReady) {
+ if (ActivityThread.isSystem()) {
+ sSystemReady =
+ LocalServices.getService(ActivityManagerInternal.class).isSystemReady();
+ } else {
+ // Since this is being called from outside system server, system should be
+ // ready by now.
+ sSystemReady = true;
+ }
+ }
+ return sSystemReady;
+ }
+
+ /**
+ * @hide
+ */
+ public static void broadcastStickyIntent(Intent intent, int userId) {
+ broadcastStickyIntent(intent, AppOpsManager.OP_NONE, null, userId);
+ }
+
+ /**
+ * Convenience for sending a sticky broadcast. For internal use only.
+ *
+ * @hide
+ */
+ public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
+ broadcastStickyIntent(intent, appOp, null, userId);
+ }
+
+ /**
+ * Convenience for sending a sticky broadcast. For internal use only.
+ *
+ * @hide
+ */
+ public static void broadcastStickyIntent(Intent intent, int appOp, Bundle options, int userId) {
+ try {
+ getService().broadcastIntentWithFeature(
+ null, null, intent, null, null, Activity.RESULT_OK, null, null,
+ null /*requiredPermissions*/, null /*excludedPermissions*/,
+ null /*excludedPackages*/, appOp, options, false, true, userId);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public static void resumeAppSwitches() throws RemoteException {
+ getService().resumeAppSwitches();
+ }
+
+ /**
+ * @hide
+ */
+ public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag) {
+ try {
+ getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, sourcePkg, tag);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
+ try {
+ getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
+ } catch (RemoteException ex) {
+ }
+ }
+
+
+ /**
+ * @hide
+ */
+ public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
+ try {
+ getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static IActivityManager getService() {
+ return IActivityManagerSingleton.get();
+ }
+
+ private static IActivityTaskManager getTaskService() {
+ return ActivityTaskManager.getService();
+ }
+
+ @UnsupportedAppUsage
+ private static final Singleton<IActivityManager> IActivityManagerSingleton =
+ new Singleton<IActivityManager>() {
+ @Override
+ protected IActivityManager create() {
+ final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
+ final IActivityManager am = IActivityManager.Stub.asInterface(b);
+ return am;
+ }
+ };
+
+ private static void dumpService(PrintWriter pw, FileDescriptor fd, String name, String[] args) {
+ pw.print("DUMP OF SERVICE "); pw.print(name); pw.println(":");
+ IBinder service = ServiceManager.checkService(name);
+ if (service == null) {
+ pw.println(" (Service not found)");
+ pw.flush();
+ return;
+ }
+ pw.flush();
+ if (service instanceof Binder) {
+ // If this is a local object, it doesn't make sense to do an async dump with it,
+ // just directly dump.
+ try {
+ service.dump(fd, args);
+ } catch (Throwable e) {
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ pw.flush();
+ }
+ } else {
+ // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+ TransferPipe tp = null;
+ try {
+ pw.flush();
+ tp = new TransferPipe();
+ tp.setBufferPrefix(" ");
+ service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+ tp.go(fd, 10000);
+ } catch (Throwable e) {
+ if (tp != null) {
+ tp.kill();
+ }
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ }
+ }
+ }
+
+ /**
+ * Request that the system start watching for the calling process to exceed a pss
+ * size as given here. Once called, the system will look for any occasions where it
+ * sees the associated process with a larger pss size and, when this happens, automatically
+ * pull a heap dump from it and allow the user to share the data. Note that this request
+ * continues running even if the process is killed and restarted. To remove the watch,
+ * use {@link #clearWatchHeapLimit()}.
+ *
+ * <p>This API only works if the calling process has been marked as
+ * {@link ApplicationInfo#FLAG_DEBUGGABLE} or this is running on a debuggable
+ * (userdebug or eng) build.</p>
+ *
+ * <p>Callers can optionally implement {@link #ACTION_REPORT_HEAP_LIMIT} to directly
+ * handle heap limit reports themselves.</p>
+ *
+ * @param pssSize The size in bytes to set the limit at.
+ */
+ public void setWatchHeapLimit(long pssSize) {
+ try {
+ getService().setDumpHeapDebugLimit(null, 0, pssSize,
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Action an app can implement to handle reports from {@link #setWatchHeapLimit(long)}.
+ * If your package has an activity handling this action, it will be launched with the
+ * heap data provided to it the same way as {@link Intent#ACTION_SEND}. Note that to
+ * match, the activity must support this action and a MIME type of "*/*".
+ */
+ public static final String ACTION_REPORT_HEAP_LIMIT = "android.app.action.REPORT_HEAP_LIMIT";
+
+ /**
+ * Clear a heap watch limit previously set by {@link #setWatchHeapLimit(long)}.
+ */
+ public void clearWatchHeapLimit() {
+ try {
+ getService().setDumpHeapDebugLimit(null, 0, 0, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether currently in lock task mode. When in this mode
+ * no new tasks can be created or switched to.
+ *
+ * @see Activity#startLockTask()
+ *
+ * @deprecated Use {@link #getLockTaskModeState} instead.
+ */
+ @Deprecated
+ public boolean isInLockTaskMode() {
+ return getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+ }
+
+ /**
+ * Return the current state of task locking. The three possible outcomes
+ * are {@link #LOCK_TASK_MODE_NONE}, {@link #LOCK_TASK_MODE_LOCKED}
+ * and {@link #LOCK_TASK_MODE_PINNED}.
+ *
+ * @see Activity#startLockTask()
+ */
+ public int getLockTaskModeState() {
+ try {
+ return getTaskService().getLockTaskModeState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads. Only one
+ * thread can be a VR thread in a process at a time, and that thread may be subject to
+ * restrictions on the amount of time it can run.
+ *
+ * If persistent VR mode is set, whatever thread has been granted aggressive scheduling via this
+ * method will return to normal operation, and calling this method will do nothing while
+ * persistent VR mode is enabled.
+ *
+ * To reset the VR thread for an application, a tid of 0 can be passed.
+ *
+ * @see android.os.Process#myTid()
+ * @param tid tid of the VR thread
+ */
+ public static void setVrThread(int tid) {
+ try {
+ getTaskService().setVrThread(tid);
+ } catch (RemoteException e) {
+ // pass
+ }
+ }
+
+ /**
+ * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads that persist
+ * beyond a single process. Only one thread can be a
+ * persistent VR thread at a time, and that thread may be subject to restrictions on the amount
+ * of time it can run. Calling this method will disable aggressive scheduling for non-persistent
+ * VR threads set via {@link #setVrThread}. If persistent VR mode is disabled then the
+ * persistent VR thread loses its new scheduling priority; this method must be called again to
+ * set the persistent thread.
+ *
+ * To reset the persistent VR thread, a tid of 0 can be passed.
+ *
+ * @see android.os.Process#myTid()
+ * @param tid tid of the VR thread
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.RESTRICTED_VR_ACCESS)
+ public static void setPersistentVrThread(int tid) {
+ try {
+ getService().setPersistentVrThread(tid);
+ } catch (RemoteException e) {
+ // pass
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.CHANGE_CONFIGURATION)
+ public void scheduleApplicationInfoChanged(List<String> packages, int userId) {
+ try {
+ getService().scheduleApplicationInfoChanged(packages, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return if a given profile is in the foreground.
+ * @param userHandle UserHandle to check
+ * @return Returns the boolean result.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public boolean isProfileForeground(@NonNull UserHandle userHandle) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager != null) {
+ for (UserInfo userInfo : userManager.getProfiles(getCurrentUser())) {
+ if (userInfo.id == userHandle.getIdentifier()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Kill the given PIDs, but the killing will be delayed until the device is idle
+ * and the given process is imperceptible.
+ *
+ * <p>You must hold the permission
+ * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to
+ * call this method.
+ * </p>
+ *
+ * @param pids The list of the pids to be killed
+ * @pram reason The reason of the kill
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
+ public void killProcessesWhenImperceptible(@NonNull int[] pids, @NonNull String reason) {
+ try {
+ getService().killProcessesWhenImperceptible(pids, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
+ return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
+ }
+
+ /** @hide */
+ public static String procStateToString(int procState) {
+ final String procStateStr;
+ switch (procState) {
+ case ActivityManager.PROCESS_STATE_PERSISTENT:
+ procStateStr = "PER ";
+ break;
+ case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
+ procStateStr = "PERU";
+ break;
+ case ActivityManager.PROCESS_STATE_TOP:
+ procStateStr = "TOP ";
+ break;
+ case ActivityManager.PROCESS_STATE_BOUND_TOP:
+ procStateStr = "BTOP";
+ break;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ procStateStr = "FGS ";
+ break;
+ case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ procStateStr = "BFGS";
+ break;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+ procStateStr = "IMPF";
+ break;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+ procStateStr = "IMPB";
+ break;
+ case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
+ procStateStr = "TRNB";
+ break;
+ case ActivityManager.PROCESS_STATE_BACKUP:
+ procStateStr = "BKUP";
+ break;
+ case ActivityManager.PROCESS_STATE_SERVICE:
+ procStateStr = "SVC ";
+ break;
+ case ActivityManager.PROCESS_STATE_RECEIVER:
+ procStateStr = "RCVR";
+ break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procStateStr = "TPSL";
+ break;
+ case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
+ procStateStr = "HVY ";
+ break;
+ case ActivityManager.PROCESS_STATE_HOME:
+ procStateStr = "HOME";
+ break;
+ case ActivityManager.PROCESS_STATE_LAST_ACTIVITY:
+ procStateStr = "LAST";
+ break;
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+ procStateStr = "CAC ";
+ break;
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ procStateStr = "CACC";
+ break;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ procStateStr = "CRE ";
+ break;
+ case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+ procStateStr = "CEM ";
+ break;
+ case ActivityManager.PROCESS_STATE_NONEXISTENT:
+ procStateStr = "NONE";
+ break;
+ default:
+ procStateStr = "??";
+ break;
+ }
+ return procStateStr;
+ }
+
+ /**
+ * The AppTask allows you to manage your own application's tasks.
+ * See {@link android.app.ActivityManager#getAppTasks()}
+ */
+ public static class AppTask {
+ private IAppTask mAppTaskImpl;
+
+ /** @hide */
+ public AppTask(IAppTask task) {
+ mAppTaskImpl = task;
+ }
+
+ /**
+ * Finishes all activities in this task and removes it from the recent tasks list.
+ */
+ public void finishAndRemoveTask() {
+ try {
+ mAppTaskImpl.finishAndRemoveTask();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the RecentTaskInfo associated with this task.
+ *
+ * @return The RecentTaskInfo for this task, or null if the task no longer exists.
+ */
+ public RecentTaskInfo getTaskInfo() {
+ try {
+ return mAppTaskImpl.getTaskInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Bring this task to the foreground. If it contains activities, they will be
+ * brought to the foreground with it and their instances re-created if needed.
+ * If it doesn't contain activities, the root activity of the task will be
+ * re-launched.
+ */
+ public void moveToFront() {
+ try {
+ ActivityThread thread = ActivityThread.currentActivityThread();
+ IApplicationThread appThread = thread.getApplicationThread();
+ String packageName = ActivityThread.currentPackageName();
+ mAppTaskImpl.moveToFront(appThread, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start an activity in this task. Brings the task to the foreground. If this task
+ * is not currently active (that is, its id < 0), then a new activity for the given
+ * Intent will be launched as the root of the task and the task brought to the
+ * foreground. Otherwise, if this task is currently active and the Intent does not specify
+ * an activity to launch in a new task, then a new activity for the given Intent will
+ * be launched on top of the task and the task brought to the foreground. If this
+ * task is currently active and the Intent specifies {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ * or would otherwise be launched in to a new task, then the activity not launched but
+ * this task be brought to the foreground and a new intent delivered to the top
+ * activity if appropriate.
+ *
+ * <p>In other words, you generally want to use an Intent here that does not specify
+ * {@link Intent#FLAG_ACTIVITY_NEW_TASK} or {@link Intent#FLAG_ACTIVITY_NEW_DOCUMENT},
+ * and let the system do the right thing.</p>
+ *
+ * @param intent The Intent describing the new activity to be launched on the task.
+ * @param options Optional launch options.
+ *
+ * @see Activity#startActivity(android.content.Intent, android.os.Bundle)
+ */
+ public void startActivity(Context context, Intent intent, Bundle options) {
+ ActivityThread thread = ActivityThread.currentActivityThread();
+ thread.getInstrumentation().execStartActivityFromAppTask(context,
+ thread.getApplicationThread(), mAppTaskImpl, intent, options);
+ }
+
+ /**
+ * Modify the {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag in the root
+ * Intent of this AppTask.
+ *
+ * @param exclude If true, {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} will
+ * be set; otherwise, it will be cleared.
+ */
+ public void setExcludeFromRecents(boolean exclude) {
+ try {
+ mAppTaskImpl.setExcludeFromRecents(exclude);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Get packages of bugreport-allowlisted apps to handle a bug report.
+ *
+ * @return packages of bugreport-allowlisted apps to handle a bug report.
+ * @hide
+ */
+ public List<String> getBugreportWhitelistedPackages() {
+ try {
+ return getService().getBugreportWhitelistedPackages();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Method for the app to tell system that it's wedged and would like to trigger an ANR.
+ *
+ * @param reason The description of that what happened
+ */
+ public void appNotResponding(@NonNull final String reason) {
+ try {
+ getService().appNotResponding(reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register to be notified when the visibility of the home screen changes.
+ *
+ * @param executor The executor on which the listener should be called.
+ * @param listener The listener that is called when home visibility changes.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ public void addHomeVisibilityListener(@NonNull Executor executor,
+ @NonNull HomeVisibilityListener listener) {
+ Preconditions.checkNotNull(listener);
+ Preconditions.checkNotNull(executor);
+ try {
+ listener.init(mContext, executor);
+ getService().registerProcessObserver(listener.mObserver);
+ // Notify upon first registration.
+ executor.execute(() ->
+ listener.onHomeVisibilityChanged(listener.mIsHomeActivityVisible));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a listener that was previously added with {@link #addHomeVisibilityListener}.
+ *
+ * @param listener The listener that was previously added.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ public void removeHomeVisibilityListener(@NonNull HomeVisibilityListener listener) {
+ Preconditions.checkNotNull(listener);
+ try {
+ getService().unregisterProcessObserver(listener.mObserver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resets the state of the {@link com.android.server.am.AppErrors} instance.
+ * This is intended for use with CTS only.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.RESET_APP_ERRORS)
+ public void resetAppErrors() {
+ try {
+ getService().resetAppErrors();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Holds the AM lock for the specified amount of milliseconds.
+ * This is intended for use by the tests that need to imitate lock contention.
+ * The token should be obtained by
+ * {@link android.content.pm.PackageManager#getHoldLockToken()}.
+ * @hide
+ */
+ @TestApi
+ public void holdLock(IBinder token, int durationMs) {
+ try {
+ getService().holdLock(token, durationMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Blocks until all broadcast queues become idle.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void waitForBroadcastIdle() {
+ try {
+ getService().waitForBroadcastIdle();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Delays delivering broadcasts to the specified package.
+ *
+ * <p> When {@code delayedDurationMs} is {@code 0}, it will clears any previously
+ * set forced delays.
+ *
+ * <p><b>Note: This method is only intended for testing and it only
+ * works for packages that are already running.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void forceDelayBroadcastDelivery(@NonNull String targetPackage,
+ @IntRange(from = 0) long delayedDurationMs) {
+ try {
+ getService().forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the "modern" broadcast queue is enabled.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public boolean isModernBroadcastQueueEnabled() {
+ try {
+ return getService().isModernBroadcastQueueEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if the process represented by the given {@code pid} is frozen.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public boolean isProcessFrozen(int pid) {
+ try {
+ return getService().isProcessFrozen(pid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Internal method for logging API starts. Used with
+ * FGS metrics logging. Is called by APIs that are
+ * used with FGS to log an API event (eg when
+ * the camera starts).
+ * @hide
+ *
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE)
+ public void noteForegroundResourceUseBegin(@ForegroundServiceApiType int apiType,
+ int uid, int pid) throws SecurityException {
+ try {
+ getService().logFgsApiBegin(apiType, uid, pid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Internal method for logging API end. Used with
+ * FGS metrics logging. Is called by APIs that are
+ * used with FGS to log an API event (eg when
+ * the camera starts).
+ * @hide
+ *
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE)
+ public void noteForegroundResourceUseEnd(@ForegroundServiceApiType int apiType,
+ int uid, int pid) throws SecurityException {
+ try {
+ getService().logFgsApiEnd(apiType, uid, pid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return The reason code of whether or not the given UID should be exempted from background
+ * restrictions here.
+ *
+ * <p>
+ * Note: Call it with caution as it'll try to acquire locks in other services.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ @ReasonCode
+ public int getBackgroundRestrictionExemptionReason(int uid) {
+ try {
+ return getService().getBackgroundRestrictionExemptionReason(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PowerExemptionManager.REASON_DENIED;
+ }
+
+ /**
+ * Notifies {@link #getRunningAppProcesses app processes} that the system properties
+ * have changed.
+ *
+ * @see SystemProperties#addChangeCallback
+ *
+ * @hide
+ */
+ @TestApi
+ public void notifySystemPropertiesChanged() {
+ // Note: this cannot use {@link ServiceManager#listServices()} to notify all the services,
+ // as that is not available from tests.
+ final var binder = ActivityManager.getService().asBinder();
+ if (binder != null) {
+ var data = Parcel.obtain();
+ try {
+ binder.transact(IBinder.SYSPROPS_TRANSACTION, data, null /* reply */,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ data.recycle();
+ }
+ }
+
+ /**
+ * A subset of immutable pending intent information suitable for caching on the client side.
+ *
+ * @hide
+ */
+ public static final class PendingIntentInfo implements Parcelable {
+
+ @Nullable private final String mCreatorPackage;
+ private final int mCreatorUid;
+ private final boolean mImmutable;
+ private final int mIntentSenderType;
+
+ public PendingIntentInfo(@Nullable String creatorPackage, int creatorUid, boolean immutable,
+ int intentSenderType) {
+ mCreatorPackage = creatorPackage;
+ mCreatorUid = creatorUid;
+ mImmutable = immutable;
+ mIntentSenderType = intentSenderType;
+ }
+
+ @Nullable
+ public String getCreatorPackage() {
+ return mCreatorPackage;
+ }
+
+ public int getCreatorUid() {
+ return mCreatorUid;
+ }
+
+ public boolean isImmutable() {
+ return mImmutable;
+ }
+
+ public int getIntentSenderType() {
+ return mIntentSenderType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeString(mCreatorPackage);
+ parcel.writeInt(mCreatorUid);
+ parcel.writeBoolean(mImmutable);
+ parcel.writeInt(mIntentSenderType);
+ }
+
+ public static final @NonNull Creator<PendingIntentInfo> CREATOR =
+ new Creator<PendingIntentInfo>() {
+ @Override
+ public PendingIntentInfo createFromParcel(Parcel in) {
+ return new PendingIntentInfo(
+ /* creatorPackage= */ in.readString(),
+ /* creatorUid= */ in.readInt(),
+ /* immutable= */ in.readBoolean(),
+ /* intentSenderType= */ in.readInt());
+ }
+
+ @Override
+ public PendingIntentInfo[] newArray(int size) {
+ return new PendingIntentInfo[size];
+ }
+ };
+ }
+}
diff --git a/android-34/android/app/ActivityManagerInternal.java b/android-34/android/app/ActivityManagerInternal.java
new file mode 100644
index 0000000..021f932
--- /dev/null
+++ b/android-34/android/app/ActivityManagerInternal.java
@@ -0,0 +1,1227 @@
+/*
+ * 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.app;
+
+import static android.app.ActivityManager.StopUserOnSwitch;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.PermissionMethod;
+import android.annotation.PermissionName;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessCapability;
+import android.app.ActivityManager.RestrictionLevel;
+import android.app.assist.ActivityId;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityPresentationInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.PowerExemptionManager.TempAllowListType;
+import android.os.TransactionTooLargeException;
+import android.os.WorkSource;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.StatsEvent;
+
+import com.android.internal.os.TimeoutRecord;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * Activity manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class ActivityManagerInternal {
+
+ public enum ServiceNotificationPolicy {
+ /**
+ * The Notification is not associated with any foreground service.
+ */
+ NOT_FOREGROUND_SERVICE,
+ /**
+ * The Notification is associated with a foreground service, but the
+ * notification system should handle it just like non-FGS notifications.
+ */
+ SHOW_IMMEDIATELY,
+ /**
+ * The Notification is associated with a foreground service, and the
+ * notification system should ignore it unless it has already been shown (in
+ * which case it should be used to update the currently displayed UI).
+ */
+ UPDATE_ONLY
+ }
+
+ // Access modes for handleIncomingUser.
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+ */
+ public static final int ALLOW_NON_FULL = 0;
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * if in the same profile group.
+ * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+ */
+ public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
+ /**
+ * Allows access to a caller only if it has the full
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+ */
+ public static final int ALLOW_FULL_ONLY = 2;
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+ * if in the same profile group.
+ * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS} is required and suffices
+ * as in {@link #ALLOW_NON_FULL}.
+ */
+ public static final int ALLOW_PROFILES_OR_NON_FULL = 3;
+
+ /**
+ * Returns profile information in free form string in two separate strings.
+ * See AppProfiler for the output format.
+ * The output can only be used for human consumption. The format may change
+ * in the future.
+ * Do not call it frequently.
+ * @param time uptime for the cpu state
+ * @param lines lines of the cpu state should be returned
+ * @return a pair of Strings. The first is the current cpu load, the second is the cpu state.
+ */
+ public abstract Pair<String, String> getAppProfileStatsForDebugging(long time, int lines);
+
+ /**
+ * Verify that calling app has access to the given provider.
+ */
+ public abstract String checkContentProviderAccess(String authority, @UserIdInt int userId);
+
+ /**
+ * Verify that calling UID has access to the given provider.
+ */
+ public abstract int checkContentProviderUriPermission(Uri uri, @UserIdInt int userId,
+ int callingUid, int modeFlags);
+
+ // Called by the power manager.
+ public abstract void onWakefulnessChanged(int wakefulness);
+
+ /**
+ * @return {@code true} if process start is successful, {@code false} otherwise.
+ */
+ public abstract boolean startIsolatedProcess(String entryPoint, String[] mainArgs,
+ String processName, String abiOverride, int uid, Runnable crashHandler);
+
+ /**
+ * Called when a user has been deleted. This can happen during normal device usage
+ * or just at startup, when partially removed users are purged. Any state persisted by the
+ * ActivityManager should be purged now.
+ *
+ * @param userId The user being cleaned up.
+ */
+ public abstract void onUserRemoved(@UserIdInt int userId);
+
+ /**
+ * Kill foreground apps from the specified user.
+ */
+ public abstract void killForegroundAppsForUser(@UserIdInt int userId);
+
+ /**
+ * Sets how long a {@link PendingIntent} can be temporarily allowlisted to bypass restrictions
+ * such as Power Save mode.
+ * @param target
+ * @param allowlistToken
+ * @param duration temp allowlist duration in milliseconds.
+ * @param type temp allowlist type defined at {@link TempAllowListType}
+ * @param reasonCode one of {@link ReasonCode}
+ * @param reason A human-readable reason for logging purposes.
+ */
+ public abstract void setPendingIntentAllowlistDuration(IIntentSender target,
+ IBinder allowlistToken, long duration, @TempAllowListType int type,
+ @ReasonCode int reasonCode, @Nullable String reason);
+
+ /**
+ * Returns the flags set for a {@link PendingIntent}.
+ */
+ public abstract int getPendingIntentFlags(IIntentSender target);
+
+ /**
+ * Allows a {@link PendingIntent} to start activities from background.
+ */
+ public abstract void setPendingIntentAllowBgActivityStarts(
+ IIntentSender target, IBinder allowlistToken, int flags);
+
+ /**
+ * Voids {@link PendingIntent}'s privilege to start activities from background.
+ */
+ public abstract void clearPendingIntentAllowBgActivityStarts(IIntentSender target,
+ IBinder allowlistToken);
+
+ /**
+ * Allow DeviceIdleController to tell us about what apps are allowlisted.
+ */
+ public abstract void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids);
+
+ /**
+ * Update information about which app IDs are on the temp allowlist.
+ * @param appids the updated list of appIds in temp allowlist.
+ * If null, it is to update only changingUid.
+ * @param changingUid uid to add or remove to temp allowlist.
+ * @param adding true to add to temp allowlist, false to remove from temp allowlist.
+ * @param durationMs when adding is true, the duration to be in temp allowlist.
+ * @param type temp allowlist type defined at {@link TempAllowListType}.
+ * @param reasonCode one of {@link ReasonCode}
+ * @param reason A human-readable reason for logging purposes.
+ * @param callingUid the callingUid that setup this temp allowlist, only valid when param adding
+ * is true.
+ */
+ public abstract void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
+ boolean adding, long durationMs, @TempAllowListType int type,
+ @ReasonCode int reasonCode,
+ @Nullable String reason, int callingUid);
+
+ /**
+ * Get the procstate for the UID. The return value will be between
+ * {@link ActivityManager#MIN_PROCESS_STATE} and {@link ActivityManager#MAX_PROCESS_STATE}.
+ * Note if the UID doesn't exist, it'll return {@link ActivityManager#PROCESS_STATE_NONEXISTENT}
+ * (-1).
+ */
+ public abstract int getUidProcessState(int uid);
+
+ /**
+ * Get a map of pid and package name that process of that pid Android/data and Android/obb
+ * directory is not mounted to lowerfs.
+ */
+ public abstract Map<Integer, String> getProcessesWithPendingBindMounts(int userId);
+
+ /**
+ * @return {@code true} if system is ready, {@code false} otherwise.
+ */
+ public abstract boolean isSystemReady();
+
+ /**
+ * @return {@code true} if system is using the "modern" broadcast queue,
+ * {@code false} otherwise.
+ */
+ public abstract boolean isModernQueueEnabled();
+
+ /**
+ * Enforce capability restrictions on use of the given BroadcastOptions
+ */
+ public abstract void enforceBroadcastOptionsPermissions(@Nullable Bundle options,
+ int callingUid);
+
+ /**
+ * Returns package name given pid.
+ *
+ * @param pid The pid we are searching package name for.
+ */
+ @Nullable
+ public abstract String getPackageNameByPid(int pid);
+
+ /**
+ * Sets if the given pid has an overlay UI or not.
+ *
+ * @param pid The pid we are setting overlay UI for.
+ * @param hasOverlayUi True if the process has overlay UI.
+ * @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
+ */
+ public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+ /**
+ * Called after the network policy rules are updated by
+ * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+ * {@param procStateSeq}.
+ */
+ public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
+
+ /**
+ * Inform ActivityManagerService about the latest {@code blockedReasons} for an uid, which
+ * can be used to understand whether the {@code uid} is allowed to access network or not.
+ */
+ public abstract void onUidBlockedReasonsChanged(int uid, int blockedReasons);
+
+ /**
+ * @return true if runtime was restarted, false if it's normal boot
+ */
+ public abstract boolean isRuntimeRestarted();
+
+ /**
+ * Returns if more users can be started without stopping currently running users.
+ */
+ public abstract boolean canStartMoreUsers();
+
+ /**
+ * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}.
+ */
+ public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage);
+
+ /**
+ * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}.
+ */
+ public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage);
+
+ /**
+ * Returns maximum number of users that can run simultaneously.
+ */
+ public abstract int getMaxRunningUsers();
+
+ /**
+ * Whether an UID is active or idle.
+ */
+ public abstract boolean isUidActive(int uid);
+
+ /**
+ * Returns a list of running processes along with corresponding uids, pids and their oom score.
+ *
+ * Only processes managed by ActivityManagerService are included.
+ */
+ public abstract List<ProcessMemoryState> getMemoryStateForProcesses();
+
+ /**
+ * Checks to see if the calling pid is allowed to handle the user. Returns adjusted user id as
+ * needed.
+ */
+ public abstract int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId,
+ boolean allowAll, int allowMode, String name, String callerPackage);
+
+ /** Checks if the calling binder pid/uid has the given permission. */
+ @PermissionMethod
+ public abstract void enforceCallingPermission(@PermissionName String permission, String func);
+
+ /**
+ * Returns the current and target user ids as a {@link Pair}. Target user id will be
+ * {@link android.os.UserHandle#USER_NULL} if there is not an ongoing user switch.
+ */
+ public abstract Pair<Integer, Integer> getCurrentAndTargetUserIds();
+
+ /** Returns the current user id. */
+ public abstract int getCurrentUserId();
+
+ /** Returns the currently started user ids. */
+ public abstract int[] getStartedUserIds();
+
+ /** Returns true if the user is running. */
+ public abstract boolean isUserRunning(@UserIdInt int userId, int flags);
+
+ /** Trims memory usage in the system by removing/stopping unused application processes. */
+ public abstract void trimApplications();
+
+ /** Kill the processes in the list due to their tasks been removed. */
+ public abstract void killProcessesForRemovedTask(ArrayList<Object> procsToKill);
+
+ /** Kill the process immediately. */
+ public abstract void killProcess(String processName, int uid, String reason);
+
+ /**
+ * Returns {@code true} if {@code uid} is running an activity from {@code packageName}.
+ */
+ public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
+
+ /**
+ * Oom Adj Reason: none - internal use only, do not use it.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_NONE = 0;
+
+ /**
+ * Oom Adj Reason: activity changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+ /**
+ * Oom Adj Reason: finishing a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+ /**
+ * Oom Adj Reason: starting a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+ /**
+ * Oom Adj Reason: binding to a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+ /**
+ * Oom Adj Reason: unbinding from a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+ /**
+ * Oom Adj Reason: starting a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+ /**
+ * Oom Adj Reason: connecting to a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+ /**
+ * Oom Adj Reason: disconnecting from a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+ /**
+ * Oom Adj Reason: UI visibility changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+ /**
+ * Oom Adj Reason: device power allowlist changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+ /**
+ * Oom Adj Reason: starting a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+ /**
+ * Oom Adj Reason: ending a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+ /**
+ * Oom Adj Reason: short FGS timeout.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+ /**
+ * Oom Adj Reason: system initialization.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+ /**
+ * Oom Adj Reason: backup/restore.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+ /**
+ * Oom Adj Reason: instrumented by the SHELL.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHELL = 16;
+
+ /**
+ * Oom Adj Reason: task stack is being removed.
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+ /**
+ * Oom Adj Reason: uid idle.
+ */
+ public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+ /**
+ * Oom Adj Reason: stop service.
+ */
+ public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+ /**
+ * Oom Adj Reason: executing service.
+ */
+ public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+ /**
+ * Oom Adj Reason: background restriction changes.
+ */
+ public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+ /**
+ * Oom Adj Reason: A package or its component is disabled.
+ */
+ public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+ @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+ OOM_ADJ_REASON_NONE,
+ OOM_ADJ_REASON_ACTIVITY,
+ OOM_ADJ_REASON_FINISH_RECEIVER,
+ OOM_ADJ_REASON_START_RECEIVER,
+ OOM_ADJ_REASON_BIND_SERVICE,
+ OOM_ADJ_REASON_UNBIND_SERVICE,
+ OOM_ADJ_REASON_START_SERVICE,
+ OOM_ADJ_REASON_GET_PROVIDER,
+ OOM_ADJ_REASON_REMOVE_PROVIDER,
+ OOM_ADJ_REASON_UI_VISIBILITY,
+ OOM_ADJ_REASON_ALLOWLIST,
+ OOM_ADJ_REASON_PROCESS_BEGIN,
+ OOM_ADJ_REASON_PROCESS_END,
+ OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+ OOM_ADJ_REASON_SYSTEM_INIT,
+ OOM_ADJ_REASON_BACKUP,
+ OOM_ADJ_REASON_SHELL,
+ OOM_ADJ_REASON_REMOVE_TASK,
+ OOM_ADJ_REASON_UID_IDLE,
+ OOM_ADJ_REASON_STOP_SERVICE,
+ OOM_ADJ_REASON_EXECUTING_SERVICE,
+ OOM_ADJ_REASON_RESTRICTION_CHANGE,
+ OOM_ADJ_REASON_COMPONENT_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OomAdjReason {}
+
+ /**
+ * Request to update oom adj.
+ */
+ public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
+ public abstract void updateCpuStats();
+
+ /**
+ * Update battery stats on activity usage.
+ * @param activity
+ * @param uid
+ * @param userId
+ * @param started
+ */
+ public abstract void updateBatteryStats(
+ ComponentName activity, int uid, @UserIdInt int userId, boolean resumed);
+
+ /**
+ * Update UsageStats of the activity.
+ * @param activity
+ * @param userId
+ * @param event
+ * @param appToken ActivityRecord's appToken.
+ * @param taskRoot Task's root
+ */
+ public abstract void updateActivityUsageStats(
+ ComponentName activity, @UserIdInt int userId, int event, IBinder appToken,
+ ComponentName taskRoot, ActivityId activityId);
+ public abstract void updateForegroundTimeIfOnBattery(
+ String packageName, int uid, long cpuTimeDiff);
+ public abstract void sendForegroundProfileChanged(@UserIdInt int userId);
+
+ /**
+ * Returns whether the given user requires credential entry at this time. This is used to
+ * intercept activity launches for locked work apps due to work challenge being triggered or
+ * when the profile user is yet to be unlocked.
+ */
+ public abstract boolean shouldConfirmCredentials(@UserIdInt int userId);
+
+ /**
+ * Used in conjunction with {@link #noteAlarmStart(PendingIntent, WorkSource, int, String)} to
+ * note an alarm duration for battery attribution
+ */
+ public abstract void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag);
+
+ /**
+ * Used in conjunction with {@link #noteAlarmFinish(PendingIntent, WorkSource, int, String)} to
+ * note an alarm duration for battery attribution
+ */
+ public abstract void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag);
+
+ /**
+ * Used to note a wakeup alarm for battery attribution.
+ */
+ public abstract void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag);
+
+ /**
+ * Returns whether this app is disallowed to run in the background.
+ *
+ * @see ActivityManager#APP_START_MODE_DISABLED
+ */
+ public abstract boolean isAppStartModeDisabled(int uid, String packageName);
+
+ /**
+ * Returns the ids of the current user and all of its profiles (if any), regardless of the
+ * running state of the profiles.
+ */
+ public abstract int[] getCurrentProfileIds();
+ public abstract UserInfo getCurrentUser();
+ public abstract void ensureNotSpecialUser(@UserIdInt int userId);
+ public abstract boolean isCurrentProfile(@UserIdInt int userId);
+ public abstract boolean hasStartedUserState(@UserIdInt int userId);
+ public abstract void finishUserSwitch(Object uss);
+
+ /** Schedule the execution of all pending app GCs. */
+ public abstract void scheduleAppGcs();
+
+ /** Gets the task id for a given activity. */
+ public abstract int getTaskIdForActivity(@NonNull IBinder token, boolean onlyRoot);
+
+ /** Gets the basic info for a given activity. */
+ public abstract ActivityPresentationInfo getActivityPresentationInfo(@NonNull IBinder token);
+
+ public abstract void setBooting(boolean booting);
+ public abstract boolean isBooting();
+ public abstract void setBooted(boolean booted);
+ public abstract boolean isBooted();
+ public abstract void finishBooting();
+
+ /**
+ * Temp allowlist a UID for PendingIntent.
+ * @param callerPid the PID that sent the PendingIntent.
+ * @param callerUid the UID that sent the PendingIntent.
+ * @param targetUid the UID that is been temp allowlisted.
+ * @param duration temp allowlist duration in milliseconds.
+ * @param type temp allowlist type defined at {@link TempAllowListType}
+ * @param reasonCode one of {@link ReasonCode}
+ * @param reason
+ */
+ public abstract void tempAllowlistForPendingIntent(int callerPid, int callerUid, int targetUid,
+ long duration, int type, @ReasonCode int reasonCode, String reason);
+
+ public abstract int broadcastIntentInPackage(String packageName, @Nullable String featureId,
+ int uid, int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
+ IApplicationThread resultToThread, IIntentReceiver resultTo, int resultCode,
+ String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
+ boolean serialized, boolean sticky, @UserIdInt int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges,
+ @Nullable int[] broadcastAllowList);
+
+ public abstract ComponentName startServiceInPackage(int uid, Intent service,
+ String resolvedType, boolean fgRequired, String callingPackage,
+ @Nullable String callingFeatureId, @UserIdInt int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges)
+ throws TransactionTooLargeException;
+
+ public abstract void disconnectActivityFromServices(Object connectionHolder);
+ public abstract void cleanUpServices(@UserIdInt int userId, ComponentName component,
+ Intent baseIntent);
+ public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, @UserIdInt int userId);
+ public abstract void ensureBootCompleted();
+ public abstract void updateOomLevelsForDisplay(int displayId);
+ public abstract boolean isActivityStartsLoggingEnabled();
+ /** Returns true if the background activity starts is enabled. */
+ public abstract boolean isBackgroundActivityStartsEnabled();
+ /**
+ * Returns The current {@link BackgroundStartPrivileges} of the UID.
+ */
+ @NonNull
+ public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid);
+ public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
+
+ /**
+ * Returns whether the app is in a state where it is allowed to schedule a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+ */
+ public abstract boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName);
+
+ /** @see com.android.server.am.ActivityManagerService#monitor */
+ public abstract void monitor();
+
+ /** Input dispatch timeout to a window, start the ANR process. Return the timeout extension,
+ * in milliseconds, or 0 to abort dispatch. */
+ public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem,
+ TimeoutRecord timeoutRecord);
+
+ public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
+ ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
+ boolean aboveSystem, TimeoutRecord timeoutRecord);
+
+ /**
+ * App started responding to input events. This signal can be used to abort the ANR process and
+ * hide the ANR dialog.
+ */
+ public abstract void inputDispatchingResumed(int pid);
+
+ /**
+ * User tapped "wait" in the ANR dialog - reschedule the dialog to be shown again at a later
+ * time.
+ * @param data AppNotRespondingDialog.Data object
+ */
+ public abstract void rescheduleAnrDialog(Object data);
+
+ /**
+ * Sends {@link android.content.Intent#ACTION_CONFIGURATION_CHANGED} with all the appropriate
+ * flags.
+ */
+ public abstract void broadcastGlobalConfigurationChanged(int changes, boolean initLocale);
+
+ /**
+ * Sends {@link android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS} with all the appropriate
+ * flags.
+ */
+ public abstract void broadcastCloseSystemDialogs(String reason);
+
+ /**
+ * Trigger an ANR for the specified process.
+ */
+ public abstract void appNotResponding(@NonNull String processName, int uid,
+ @NonNull TimeoutRecord timeoutRecord);
+
+ /**
+ * Kills all background processes, except those matching any of the specified properties.
+ *
+ * @param minTargetSdk the target SDK version at or above which to preserve processes,
+ * or {@code -1} to ignore the target SDK
+ * @param maxProcState the process state at or below which to preserve processes,
+ * or {@code -1} to ignore the process state
+ */
+ public abstract void killAllBackgroundProcessesExcept(int minTargetSdk, int maxProcState);
+
+ /** Starts a given process. */
+ public abstract void startProcess(String processName, ApplicationInfo info,
+ boolean knownToBeDead, boolean isTop, String hostingType, ComponentName hostingName);
+
+ /** Starts up the starting activity process for debugging if needed.
+ * This function needs to be called synchronously from WindowManager context so the caller
+ * passes a lock {@code wmLock} and waits to be notified.
+ *
+ * @param wmLock calls {@code notify} on the object to wake up the caller.
+ */
+ public abstract void setDebugFlagsForStartingActivity(ActivityInfo aInfo, int startFlags,
+ ProfilerInfo profilerInfo, Object wmLock);
+
+ /** Returns mount mode for process running with given pid */
+ public abstract int getStorageMountMode(int pid, int uid);
+
+ /** Returns true if the given uid is the app in the foreground. */
+ public abstract boolean isAppForeground(int uid);
+
+ /** Returns true if the given process name and uid is currently marked 'bad' */
+ public abstract boolean isAppBad(String processName, int uid);
+
+ /** Remove pending backup for the given userId. */
+ public abstract void clearPendingBackup(@UserIdInt int userId);
+
+ /**
+ * When power button is very long pressed, call this interface to do some pre-shutdown work
+ * like persisting database etc.
+ */
+ public abstract void prepareForPossibleShutdown();
+
+ /**
+ * Returns {@code true} if {@code uid} is running a foreground service of a specific
+ * {@code foregroundServiceType}.
+ */
+ public abstract boolean hasRunningForegroundService(int uid, int foregroundServiceType);
+
+ /**
+ * Returns {@code true} if the given notification channel currently has a
+ * notification associated with a foreground service. This is an AMS check
+ * because that is the source of truth for the FGS state.
+ */
+ public abstract boolean hasForegroundServiceNotification(String pkg, @UserIdInt int userId,
+ String channelId);
+
+ /**
+ * Tell the service lifecycle logic that the given Notification content is now
+ * canonical for any foreground-service visibility policy purposes.
+ *
+ * Returns a description of any FGs-related policy around the given Notification:
+ * not associated with an FGS; ensure display; or only update if already displayed.
+ */
+ public abstract ServiceNotificationPolicy applyForegroundServiceNotification(
+ Notification notification, String tag, int id, String pkg, @UserIdInt int userId);
+
+ /**
+ * Callback from the notification subsystem that the given FGS notification has
+ * been evaluated, and either shown or explicitly overlooked. This can happen
+ * after either Service.startForeground() or NotificationManager.notify().
+ */
+ public abstract void onForegroundServiceNotificationUpdate(boolean shown,
+ Notification notification, int id, String pkg, @UserIdInt int userId);
+
+ /**
+ * Fully stop the given app's processes without restoring service starts or
+ * bindings, but without the other durable effects of the full-scale
+ * "force stop" intervention.
+ */
+ public abstract void stopAppForUser(String pkg, @UserIdInt int userId);
+
+ /**
+ * Registers the specified {@code processObserver} to be notified of future changes to
+ * process state.
+ */
+ public abstract void registerProcessObserver(IProcessObserver processObserver);
+
+ /**
+ * Unregisters the specified {@code processObserver}.
+ */
+ public abstract void unregisterProcessObserver(IProcessObserver processObserver);
+
+ /**
+ * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
+ * targets the given uid.
+ *
+ * @param uid The uid to be checked for
+ *
+ * @return the uid of the instrumentation source, if there is an instrumentation whose target
+ * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
+ */
+ public abstract int getInstrumentationSourceUid(int uid);
+
+ /** Is this a device owner app? */
+ public abstract boolean isDeviceOwner(int uid);
+
+ /**
+ * Called by DevicePolicyManagerService to set the uid of the device owner.
+ */
+ public abstract void setDeviceOwnerUid(int uid);
+
+ /** Is this a profile owner app? */
+ public abstract boolean isProfileOwner(int uid);
+
+ /**
+ * Called by DevicePolicyManagerService to set the uid of the profile owner.
+ * @param profileOwnerUids The profile owner UIDs. The ownership of the array is
+ * passed to callee.
+ */
+ public abstract void setProfileOwnerUid(ArraySet<Integer> profileOwnerUids);
+
+ /**
+ * Set all associated companion app that belongs to a userId.
+ * @param userId
+ * @param companionAppUids ActivityManager will take ownership of this Set, the caller
+ * shouldn't touch this Set after calling this interface.
+ */
+ public abstract void setCompanionAppUids(int userId, Set<Integer> companionAppUids);
+
+ /**
+ * is the uid an associated companion app of a userId?
+ * @param userId
+ * @param uid
+ * @return
+ */
+ public abstract boolean isAssociatedCompanionApp(int userId, int uid);
+
+ /**
+ * Sends a broadcast, assuming the caller to be the system and allowing the inclusion of an
+ * approved allowlist of app Ids >= {@link android.os.Process#FIRST_APPLICATION_UID} that the
+ * broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are
+ * automatically allowlisted.
+ *
+ * @param filterExtrasForReceiver A function to filter intent extras for the given receiver by
+ * using the rules of package visibility. Returns extras with legitimate package info that the
+ * receiver is able to access, or {@code null} if none of the packages is visible to the
+ * receiver.
+ * @param serialized Specifies whether or not the broadcast should be delivered to the
+ * receivers in a serial order.
+ *
+ * @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
+ * IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
+ * String[], int, Bundle, boolean, boolean, int)
+ */
+ public abstract int broadcastIntent(Intent intent,
+ IIntentReceiver resultTo,
+ String[] requiredPermissions, boolean serialized,
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions);
+
+ /**
+ * Variant of
+ * {@link #broadcastIntent(Intent, IIntentReceiver, String[], boolean, int, int[], BiFunction, Bundle)}
+ * that allows sender to receive a finish callback once the broadcast delivery is completed,
+ * but provides no ordering guarantee for how the broadcast is delivered to receivers.
+ */
+ public abstract int broadcastIntentWithCallback(Intent intent,
+ IIntentReceiver resultTo,
+ String[] requiredPermissions,
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions);
+
+ /**
+ * Add uid to the ActivityManagerService PendingStartActivityUids list.
+ * @param uid uid
+ * @param pid pid of the ProcessRecord that is pending top.
+ */
+ public abstract void addPendingTopUid(int uid, int pid, @Nullable IApplicationThread thread);
+
+ /**
+ * Delete uid from the ActivityManagerService PendingStartActivityUids list.
+ * @param uid uid
+ * @param nowElapsed starting time of updateOomAdj
+ */
+ public abstract void deletePendingTopUid(int uid, long nowElapsed);
+
+ /**
+ * Is the uid in ActivityManagerService PendingStartActivityUids list?
+ * @param uid
+ * @return true if exists, false otherwise.
+ */
+ public abstract boolean isPendingTopUid(int uid);
+
+ /**
+ * @return the intent for the given intent sender.
+ */
+ @Nullable
+ public abstract Intent getIntentForIntentSender(IIntentSender sender);
+
+ /**
+ * Effectively PendingIntent.getActivityForUser(), but the PendingIntent is
+ * owned by the given uid rather than by the caller (i.e. the system).
+ */
+ public abstract PendingIntent getPendingIntentActivityAsApp(
+ int requestCode, @NonNull Intent intent, int flags, Bundle options,
+ String ownerPkgName, int ownerUid);
+
+ /**
+ * Effectively PendingIntent.getActivityForUser(), but the PendingIntent is
+ * owned by the given uid rather than by the caller (i.e. the system).
+ */
+ public abstract PendingIntent getPendingIntentActivityAsApp(
+ int requestCode, @NonNull Intent[] intents, int flags, Bundle options,
+ String ownerPkgName, int ownerUid);
+
+ /**
+ * @return mBootTimeTempAllowlistDuration of ActivityManagerConstants.
+ */
+ public abstract long getBootTimeTempAllowListDuration();
+
+ /** Register an {@link AnrController} to control the ANR dialog behavior */
+ public abstract void registerAnrController(AnrController controller);
+
+ /** Unregister an {@link AnrController} */
+ public abstract void unregisterAnrController(AnrController controller);
+
+ /**
+ * Is the FGS started from an uid temporarily allowed to have while-in-use permission?
+ */
+ public abstract boolean isTempAllowlistedForFgsWhileInUse(int uid);
+
+ /**
+ * Return the temp allowlist type when server push messaging is over the quota.
+ */
+ public abstract @TempAllowListType int getPushMessagingOverQuotaBehavior();
+
+ /**
+ * Return the startForeground() grace period after calling startForegroundService().
+ */
+ public abstract int getServiceStartForegroundTimeout();
+
+ /**
+ * Returns the capability of the given uid
+ */
+ public abstract @ProcessCapability int getUidCapability(int uid);
+
+ /**
+ * @return The PID list of the isolated process with packages matching the given uid.
+ */
+ @Nullable
+ public abstract List<Integer> getIsolatedProcesses(int uid);
+
+ /** @see ActivityManagerService#sendIntentSender */
+ public abstract int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,
+ Intent intent, String resolvedType,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options);
+
+ /**
+ * Sets the provider to communicate between voice interaction manager service and
+ * ActivityManagerService.
+ */
+ public abstract void setVoiceInteractionManagerProvider(
+ @Nullable VoiceInteractionManagerProvider provider);
+
+ /**
+ * Sets whether the current foreground user (and its profiles) should be stopped after switched
+ * out.
+ */
+ public abstract void setStopUserOnSwitch(@StopUserOnSwitch int value);
+
+ /**
+ * Provides the interface to communicate between voice interaction manager service and
+ * ActivityManagerService.
+ */
+ public interface VoiceInteractionManagerProvider {
+ /**
+ * Notifies the service when an activity is destroyed.
+ */
+ void notifyActivityDestroyed(IBinder activityToken);
+ }
+
+ /**
+ * Get the restriction level of the given UID, if it hosts multiple packages,
+ * return least restricted level.
+ */
+ public abstract @RestrictionLevel int getRestrictionLevel(int uid);
+
+ /**
+ * Get the restriction level of the given package for given user id.
+ */
+ public abstract @RestrictionLevel int getRestrictionLevel(String pkg, @UserIdInt int userId);
+
+ /**
+ * Get whether or not apps would be put into restricted standby bucket automatically
+ * when it's background-restricted.
+ */
+ public abstract boolean isBgAutoRestrictedBucketFeatureFlagEnabled();
+
+ /**
+ * A listener interface, which will be notified on background restriction changes.
+ */
+ public interface AppBackgroundRestrictionListener {
+ /**
+ * Called when the background restriction level of given uid/package is changed.
+ */
+ default void onRestrictionLevelChanged(int uid, String packageName,
+ @RestrictionLevel int newLevel) {
+ }
+
+ /**
+ * Called when toggling the feature flag of moving to restricted standby bucket
+ * automatically on background-restricted.
+ */
+ default void onAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+ }
+ }
+
+ /**
+ * Register the background restriction listener callback.
+ */
+ public abstract void addAppBackgroundRestrictionListener(
+ @NonNull AppBackgroundRestrictionListener listener);
+
+ /**
+ * A listener interface, which will be notified on foreground service state changes.
+ */
+ public interface ForegroundServiceStateListener {
+ /**
+ * Call when the given process's foreground service state changes.
+ *
+ * @param packageName The package name of the process.
+ * @param uid The UID of the process.
+ * @param pid The pid of the process.
+ * @param started {@code true} if the process transits from non-FGS state to FGS state.
+ */
+ void onForegroundServiceStateChanged(String packageName, int uid, int pid, boolean started);
+
+ /**
+ * Call when the notification of the foreground service is updated.
+ *
+ * @param packageName The package name of the process.
+ * @param uid The UID of the process.
+ * @param foregroundId The current foreground service notification ID.
+ * @param canceling The given notification is being canceled.
+ */
+ void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId,
+ boolean canceling);
+ }
+
+ /**
+ * Register the foreground service state change listener callback.
+ */
+ public abstract void addForegroundServiceStateListener(
+ @NonNull ForegroundServiceStateListener listener);
+
+ /**
+ * A listener interface, which will be notified on the package sends a broadcast.
+ */
+ public interface BroadcastEventListener {
+ /**
+ * Called when the given package/uid is sending a broadcast.
+ */
+ void onSendingBroadcast(String packageName, int uid);
+ }
+
+ /**
+ * Register the broadcast event listener callback.
+ */
+ public abstract void addBroadcastEventListener(@NonNull BroadcastEventListener listener);
+
+ /**
+ * A listener interface, which will be notified on the package binding to a service.
+ */
+ public interface BindServiceEventListener {
+ /**
+ * Called when the given package/uid is binding to a service
+ */
+ void onBindingService(String packageName, int uid);
+ }
+
+ /**
+ * Register the bind service event listener callback.
+ */
+ public abstract void addBindServiceEventListener(@NonNull BindServiceEventListener listener);
+
+ /**
+ * Restart android.
+ */
+ public abstract void restart();
+
+ /**
+ * Returns some summary statistics of the current PendingIntent queue - sizes and counts.
+ */
+ public abstract List<PendingIntentStats> getPendingIntentStats();
+
+ /**
+ * Register the UidObserver for NetworkPolicyManager service.
+ *
+ * This is equivalent to calling
+ * {@link IActivityManager#registerUidObserver(IUidObserver, int, int, String)} but having a
+ * separate method for NetworkPolicyManager service so that it's UidObserver can be called
+ * separately outside the usual UidObserver flow.
+ */
+ public abstract void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
+ int which, int cutpoint, @NonNull String callingPackage);
+
+ /**
+ * Return all client package names of a service.
+ */
+ public abstract ArraySet<String> getClientPackages(String servicePackageName);
+
+ /**
+ * Retrieve an IUnsafeIntentStrictModeCallback matching the given callingUid.
+ * Returns null no match is found.
+ * @param callingPid The PID mapped with the callback.
+ * @return The callback, if it exists.
+ */
+ public abstract IUnsafeIntentStrictModeCallback getRegisteredStrictModeCallback(
+ int callingPid);
+
+ /**
+ * Unregisters an IUnsafeIntentStrictModeCallback matching the given callingUid.
+ * @param callingPid The PID mapped with the callback.
+ */
+ public abstract void unregisterStrictModeCallback(int callingPid);
+
+ /**
+ * Start a foreground service delegate.
+ * @param options foreground service delegate options.
+ * @param connection a service connection served as callback to caller.
+ * @return true if delegate is started successfully, false otherwise.
+ * @hide
+ */
+ public abstract boolean startForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection);
+
+ /**
+ * Stop a foreground service delegate.
+ * @param options the foreground service delegate options.
+ * @hide
+ */
+ public abstract void stopForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options);
+
+ /**
+ * Stop a foreground service delegate by service connection.
+ * @param connection service connection used to start delegate previously.
+ * @hide
+ */
+ public abstract void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
+
+ /**
+ * Same as {@link android.app.IActivityManager#startProfile(int userId)}, but it would succeed
+ * even if the profile is disabled - it should only be called by
+ * {@link com.android.server.devicepolicy.DevicePolicyManagerService} when starting a profile
+ * while it's being created.
+ */
+ public abstract boolean startProfileEvenWhenDisabled(@UserIdInt int userId);
+
+ /**
+ * Internal method for logging foreground service API journey start.
+ * Used with FGS metrics logging
+ *
+ * @hide
+ */
+ public abstract void logFgsApiBegin(int apiType, int uid, int pid);
+
+ /**
+ * Internal method for logging foreground service API journey end.
+ * Used with FGS metrics logging
+ *
+ * @hide
+ */
+ public abstract void logFgsApiEnd(int apiType, int uid, int pid);
+
+ /**
+ * Temporarily allow foreground service started by an uid to have while-in-use permission
+ * for durationMs.
+ *
+ * @param uid The UID of the app that starts the foreground service.
+ * @param durationMs elapsedRealTime duration in milliseconds.
+ * @hide
+ */
+ public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
+ /**
+ * The list of the events about the {@link android.media.projection.IMediaProjection} itself.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ MEDIA_PROJECTION_TOKEN_EVENT_CREATED,
+ MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED,
+ })
+ public @interface MediaProjectionTokenEvent{};
+
+ /**
+ * An instance of {@link android.media.projection.IMediaProjection} has been created
+ * by the system.
+ *
+ * @hide
+ */
+ public static final @MediaProjectionTokenEvent int MEDIA_PROJECTION_TOKEN_EVENT_CREATED = 0;
+
+ /**
+ * An instance of {@link android.media.projection.IMediaProjection} has been destroyed
+ * by the system.
+ *
+ * @hide
+ */
+ public static final @MediaProjectionTokenEvent int MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED = 1;
+
+ /**
+ * Called after the system created/destroyed a media projection for an app, if the user
+ * has granted the permission to start a media projection from this app.
+ *
+ * <p>This API is specifically for the use case of enforcing the FGS type
+ * {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION},
+ * where the app who is starting this type of FGS must have been granted with the permission
+ * to start the projection via the {@link android.media.projection.MediaProjection} APIs.
+ *
+ * @param uid The uid of the app which the system created/destroyed a media projection for.
+ * @param projectionToken The {@link android.media.projection.IMediaProjection} token that
+ * the system created/destroyed.
+ * @param event The actual event happening to the given {@code projectionToken}.
+ */
+ public abstract void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken,
+ @MediaProjectionTokenEvent int event);
+
+ /**
+ * @return The stats event for the cached apps high watermark since last pull.
+ */
+ @NonNull
+ public abstract StatsEvent getCachedAppsHighWatermarkStats(int atomTag, boolean resetAfterPull);
+}
diff --git a/android-34/android/app/ActivityManagerNative.java b/android-34/android/app/ActivityManagerNative.java
new file mode 100644
index 0000000..b9eb957
--- /dev/null
+++ b/android-34/android/app/ActivityManagerNative.java
@@ -0,0 +1,104 @@
+/*
+ * 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.app;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * {@hide}
+ * @deprecated will be removed soon. See individual methods for alternatives.
+ */
+@Deprecated
+public abstract class ActivityManagerNative {
+
+ @UnsupportedAppUsage
+ public ActivityManagerNative() {
+ }
+
+ /**
+ * Cast a Binder object into an activity manager interface, generating
+ * a proxy if needed.
+ *
+ * @deprecated use IActivityManager.Stub.asInterface instead.
+ */
+ @UnsupportedAppUsage
+ static public IActivityManager asInterface(IBinder obj) {
+ return IActivityManager.Stub.asInterface(obj);
+ }
+
+ /**
+ * Retrieve the system's default/global activity manager.
+ *
+ * @deprecated use ActivityManager.getService instead.
+ */
+ @UnsupportedAppUsage
+ static public IActivityManager getDefault() {
+ return ActivityManager.getService();
+ }
+
+ /**
+ * Convenience for checking whether the system is ready. For internal use only.
+ *
+ * @deprecated use ActivityManagerInternal.isSystemReady instead.
+ */
+ @UnsupportedAppUsage
+ static public boolean isSystemReady() {
+ return ActivityManager.isSystemReady();
+ }
+
+ /**
+ * @deprecated use ActivityManager.broadcastStickyIntent instead.
+ */
+ @UnsupportedAppUsage
+ static public void broadcastStickyIntent(Intent intent, String permission, int userId) {
+ broadcastStickyIntent(intent, permission, AppOpsManager.OP_NONE, userId);
+ }
+
+ /**
+ * Convenience for sending a sticky broadcast. For internal use only.
+ * If you don't care about permission, use null.
+ *
+ * @deprecated use ActivityManager.broadcastStickyIntent instead.
+ */
+ static public void broadcastStickyIntent(Intent intent, String permission, int appOp,
+ int userId) {
+ ActivityManager.broadcastStickyIntent(intent, appOp, userId);
+ }
+
+ /**
+ * @deprecated use ActivityManager.noteWakeupAlarm instead.
+ */
+ static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
+ String tag) {
+ ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag);
+ }
+
+ /**
+ * @deprecated use ActivityManager.noteAlarmStart instead.
+ */
+ static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
+ ActivityManager.noteAlarmStart(ps, null, sourceUid, tag);
+ }
+
+ /**
+ * @deprecated use ActivityManager.noteAlarmFinish instead.
+ */
+ static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
+ ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag);
+ }
+}
diff --git a/android-34/android/app/ActivityOptions.java b/android-34/android/app/ActivityOptions.java
new file mode 100644
index 0000000..2ae7216
--- /dev/null
+++ b/android-34/android/app/ActivityOptions.java
@@ -0,0 +1,2608 @@
+/*
+ * 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.app;
+
+import static android.Manifest.permission.CONTROL_KEYGUARD;
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks;
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.transition.TransitionManager;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RemoteAnimationAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.window.RemoteTransition;
+import android.window.SplashScreen;
+import android.window.WindowContainerToken;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Helper class for building an options Bundle that can be used with
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
+ * Context.startActivity(Intent, Bundle)} and related methods.
+ */
+public class ActivityOptions extends ComponentOptions {
+ private static final String TAG = "ActivityOptions";
+
+ /**
+ * A long in the extras delivered by {@link #requestUsageTimeReport} that contains
+ * the total time (in ms) the user spent in the app flow.
+ */
+ public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
+
+ /**
+ * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains
+ * detailed information about the time spent in each package associated with the app;
+ * each key is a package name, whose value is a long containing the time (in ms).
+ */
+ public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+
+ /** No explicit value chosen. The system will decide whether to grant privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ /** Allow the {@link PendingIntent} to use the background activity start privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ /** Deny the {@link PendingIntent} to use the background activity start privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+
+ /**
+ * The package name that created the options.
+ * @hide
+ */
+ public static final String KEY_PACKAGE_NAME = "android:activity.packageName";
+
+ /**
+ * The bounds (window size) that the activity should be launched in. Set to null explicitly for
+ * full screen. If the key is not found, previous bounds will be preserved.
+ * NOTE: This value is ignored on devices that don't have
+ * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
+ * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
+ * @hide
+ */
+ public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds";
+
+ /**
+ * Type of animation that arguments specify.
+ * @hide
+ */
+ public static final String KEY_ANIM_TYPE = "android:activity.animType";
+
+ /**
+ * Custom enter animation resource ID.
+ * @hide
+ */
+ public static final String KEY_ANIM_ENTER_RES_ID = "android:activity.animEnterRes";
+
+ /**
+ * Custom exit animation resource ID.
+ * @hide
+ */
+ public static final String KEY_ANIM_EXIT_RES_ID = "android:activity.animExitRes";
+
+ /**
+ * Custom in-place animation resource ID.
+ * @hide
+ */
+ public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes";
+
+ /**
+ * Custom background color for animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_BACKGROUND_COLOR = "android:activity.backgroundColor";
+
+ /**
+ * Bitmap for thumbnail animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_THUMBNAIL = "android:activity.animThumbnail";
+
+ /**
+ * Start X position of thumbnail animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_START_X = "android:activity.animStartX";
+
+ /**
+ * Start Y position of thumbnail animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_START_Y = "android:activity.animStartY";
+
+ /**
+ * Initial width of the animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_WIDTH = "android:activity.animWidth";
+
+ /**
+ * Initial height of the animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_HEIGHT = "android:activity.animHeight";
+
+ /**
+ * Callback for when animation is started.
+ * @hide
+ */
+ public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
+
+ /**
+ * Specific a theme for a splash screen window.
+ * @hide
+ */
+ public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
+
+ /**
+ * Indicates that this activity launch is eligible to show a legacy permission prompt
+ * @hide
+ */
+ public static final String KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE =
+ "android:activity.legacyPermissionPromptEligible";
+
+ /**
+ * Callback for when the last frame of the animation is played.
+ * @hide
+ */
+ private static final String KEY_ANIMATION_FINISHED_LISTENER =
+ "android:activity.animationFinishedListener";
+
+ /**
+ * Descriptions of app transition animations to be played during the activity launch.
+ */
+ private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
+
+ /**
+ * Whether the activity should be launched into LockTask mode.
+ * @see #setLockTaskEnabled(boolean)
+ */
+ private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
+
+ /**
+ * Whether the launching app's identity should be available to the launched activity.
+ * @see #setShareIdentityEnabled(boolean)
+ */
+ private static final String KEY_SHARE_IDENTITY = "android:activity.shareIdentity";
+
+ /**
+ * The display id the activity should be launched into.
+ * @see #setLaunchDisplayId(int)
+ * @hide
+ */
+ private static final String KEY_LAUNCH_DISPLAY_ID = "android.activity.launchDisplayId";
+
+ /**
+ * The id of the display where the caller was on.
+ * @see #setCallerDisplayId(int)
+ * @hide
+ */
+ private static final String KEY_CALLER_DISPLAY_ID = "android.activity.callerDisplayId";
+
+ /**
+ * The task display area token the activity should be launched into.
+ * @see #setLaunchTaskDisplayArea(WindowContainerToken)
+ * @hide
+ */
+ private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN =
+ "android.activity.launchTaskDisplayAreaToken";
+
+ /**
+ * The task display area feature id the activity should be launched into.
+ * @see #setLaunchTaskDisplayAreaFeatureId(int)
+ * @hide
+ */
+ private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID =
+ "android.activity.launchTaskDisplayAreaFeatureId";
+
+ /**
+ * The root task token the activity should be launched into.
+ * @see #setLaunchRootTask(WindowContainerToken)
+ * @hide
+ */
+ public static final String KEY_LAUNCH_ROOT_TASK_TOKEN =
+ "android.activity.launchRootTaskToken";
+
+ /**
+ * The {@link com.android.server.wm.TaskFragment} token the activity should be launched into.
+ * @see #setLaunchTaskFragmentToken(IBinder)
+ * @hide
+ */
+ public static final String KEY_LAUNCH_TASK_FRAGMENT_TOKEN =
+ "android.activity.launchTaskFragmentToken";
+
+ /**
+ * The windowing mode the activity should be launched into.
+ * @hide
+ */
+ private static final String KEY_LAUNCH_WINDOWING_MODE = "android.activity.windowingMode";
+
+ /**
+ * The activity type the activity should be launched as.
+ * @hide
+ */
+ private static final String KEY_LAUNCH_ACTIVITY_TYPE = "android.activity.activityType";
+
+ /**
+ * The task id the activity should be launched into.
+ * @hide
+ */
+ private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
+
+ /**
+ * See {@link #setDisableStartingWindow}.
+ * @hide
+ */
+ private static final String KEY_DISABLE_STARTING_WINDOW = "android.activity.disableStarting";
+
+ /**
+ * See {@link #setPendingIntentLaunchFlags(int)}
+ * @hide
+ */
+ private static final String KEY_PENDING_INTENT_LAUNCH_FLAGS =
+ "android.activity.pendingIntentLaunchFlags";
+
+ /**
+ * See {@link #setTaskAlwaysOnTop}.
+ * @hide
+ */
+ private static final String KEY_TASK_ALWAYS_ON_TOP = "android.activity.alwaysOnTop";
+
+ /**
+ * See {@link #setTaskOverlay}.
+ * @hide
+ */
+ private static final String KEY_TASK_OVERLAY = "android.activity.taskOverlay";
+
+ /**
+ * See {@link #setTaskOverlay}.
+ * @hide
+ */
+ private static final String KEY_TASK_OVERLAY_CAN_RESUME =
+ "android.activity.taskOverlayCanResume";
+
+ /**
+ * See {@link #setAvoidMoveToFront()}.
+ * @hide
+ */
+ private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront";
+
+ /**
+ * See {@link #setFreezeRecentTasksReordering()}.
+ * @hide
+ */
+ private static final String KEY_FREEZE_RECENT_TASKS_REORDERING =
+ "android.activity.freezeRecentTasksReordering";
+
+ /**
+ * Determines whether to disallow the outgoing activity from entering picture-in-picture as the
+ * result of a new activity being launched.
+ * @hide
+ */
+ private static final String KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING =
+ "android:activity.disallowEnterPictureInPictureWhileLaunching";
+
+ /**
+ * Indicates flags should be applied to the launching activity such that it will behave
+ * correctly in a bubble.
+ * @hide
+ */
+ private static final String KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES =
+ "android:activity.applyActivityFlagsForBubbles";
+
+ /**
+ * Indicates to apply {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the launching shortcut.
+ * @hide
+ */
+ private static final String KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT =
+ "android:activity.applyMultipleTaskFlagForShortcut";
+
+ /**
+ * Indicates to apply {@link Intent#FLAG_ACTIVITY_NO_USER_ACTION} to the launching shortcut.
+ * @hide
+ */
+ private static final String KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT =
+ "android:activity.applyNoUserActionFlagForShortcut";
+
+ /**
+ * For Activity transitions, the calling Activity's TransitionListener used to
+ * notify the called Activity when the shared element and the exit transitions
+ * complete.
+ */
+ private static final String KEY_TRANSITION_COMPLETE_LISTENER
+ = "android:activity.transitionCompleteListener";
+
+ private static final String KEY_TRANSITION_IS_RETURNING
+ = "android:activity.transitionIsReturning";
+ private static final String KEY_TRANSITION_SHARED_ELEMENTS
+ = "android:activity.sharedElementNames";
+ private static final String KEY_RESULT_DATA = "android:activity.resultData";
+ private static final String KEY_RESULT_CODE = "android:activity.resultCode";
+ private static final String KEY_EXIT_COORDINATOR_INDEX
+ = "android:activity.exitCoordinatorIndex";
+
+ /** See {@link SourceInfo}. */
+ private static final String KEY_SOURCE_INFO = "android.activity.sourceInfo";
+
+ private static final String KEY_USAGE_TIME_REPORT = "android:activity.usageTimeReport";
+ private static final String KEY_ROTATION_ANIMATION_HINT = "android:activity.rotationAnimationHint";
+
+ private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE
+ = "android:instantapps.installerbundle";
+ private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
+ private static final String KEY_REMOTE_ANIMATION_ADAPTER
+ = "android:activity.remoteAnimationAdapter";
+ private static final String KEY_REMOTE_TRANSITION =
+ "android:activity.remoteTransition";
+
+ private static final String KEY_OVERRIDE_TASK_TRANSITION =
+ "android:activity.overrideTaskTransition";
+
+ /** See {@link #setRemoveWithTaskOrganizer(boolean)}. */
+ private static final String KEY_REMOVE_WITH_TASK_ORGANIZER =
+ "android.activity.removeWithTaskOrganizer";
+ /** See {@link #setLaunchedFromBubble(boolean)}. */
+ private static final String KEY_LAUNCHED_FROM_BUBBLE =
+ "android.activity.launchTypeBubble";
+
+ /** See {@link #setSplashScreenStyle(int)}. */
+ private static final String KEY_SPLASH_SCREEN_STYLE =
+ "android.activity.splashScreenStyle";
+
+ /**
+ * See {@link #setTransientLaunch()}.
+ * @hide
+ */
+ public static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
+
+ /** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */
+ private static final String KEY_LAUNCH_INTO_PIP_PARAMS =
+ "android.activity.launchIntoPipParams";
+
+ /** See {@link #setDismissKeyguard()}. */
+ private static final String KEY_DISMISS_KEYGUARD = "android.activity.dismissKeyguard";
+
+ private static final String KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE =
+ "android.activity.pendingIntentCreatorBackgroundActivityStartMode";
+
+ /**
+ * @see #setLaunchCookie
+ * @hide
+ */
+ public static final String KEY_LAUNCH_COOKIE = "android.activity.launchCookie";
+
+ /** @hide */
+ public static final int ANIM_UNDEFINED = -1;
+ /** @hide */
+ public static final int ANIM_NONE = 0;
+ /** @hide */
+ public static final int ANIM_CUSTOM = 1;
+ /** @hide */
+ public static final int ANIM_SCALE_UP = 2;
+ /** @hide */
+ public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
+ /** @hide */
+ public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
+ /** @hide */
+ public static final int ANIM_SCENE_TRANSITION = 5;
+ /** @hide */
+ public static final int ANIM_DEFAULT = 6;
+ /** @hide */
+ public static final int ANIM_LAUNCH_TASK_BEHIND = 7;
+ /** @hide */
+ public static final int ANIM_THUMBNAIL_ASPECT_SCALE_UP = 8;
+ /** @hide */
+ public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9;
+ /** @hide */
+ public static final int ANIM_CUSTOM_IN_PLACE = 10;
+ /** @hide */
+ public static final int ANIM_CLIP_REVEAL = 11;
+ /** @hide */
+ public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
+ /** @hide */
+ public static final int ANIM_REMOTE_ANIMATION = 13;
+ /** @hide */
+ public static final int ANIM_FROM_STYLE = 14;
+
+ private String mPackageName;
+ private Rect mLaunchBounds;
+ private int mAnimationType = ANIM_UNDEFINED;
+ private int mCustomEnterResId;
+ private int mCustomExitResId;
+ private int mCustomInPlaceResId;
+ private int mCustomBackgroundColor;
+ private Bitmap mThumbnail;
+ private int mStartX;
+ private int mStartY;
+ private int mWidth;
+ private int mHeight;
+ private IRemoteCallback mAnimationStartedListener;
+ private IRemoteCallback mAnimationFinishedListener;
+ private ResultReceiver mTransitionReceiver;
+ private boolean mIsReturning;
+ private ArrayList<String> mSharedElementNames;
+ private Intent mResultData;
+ private int mResultCode;
+ private int mExitCoordinatorIndex;
+ private PendingIntent mUsageTimeReport;
+ private int mLaunchDisplayId = INVALID_DISPLAY;
+ private int mCallerDisplayId = INVALID_DISPLAY;
+ private WindowContainerToken mLaunchTaskDisplayArea;
+ private int mLaunchTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
+ private WindowContainerToken mLaunchRootTask;
+ private IBinder mLaunchTaskFragmentToken;
+ @WindowConfiguration.WindowingMode
+ private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
+ @WindowConfiguration.ActivityType
+ private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
+ private int mLaunchTaskId = -1;
+ private int mPendingIntentLaunchFlags;
+ private boolean mLockTaskMode = false;
+ private boolean mShareIdentity = false;
+ private boolean mDisallowEnterPictureInPictureWhileLaunching;
+ private boolean mApplyActivityFlagsForBubbles;
+ private boolean mApplyMultipleTaskFlagForShortcut;
+ private boolean mApplyNoUserActionFlagForShortcut;
+ private boolean mTaskAlwaysOnTop;
+ private boolean mTaskOverlay;
+ private boolean mTaskOverlayCanResume;
+ private boolean mAvoidMoveToFront;
+ private boolean mFreezeRecentTasksReordering;
+ private AppTransitionAnimationSpec mAnimSpecs[];
+ private SourceInfo mSourceInfo;
+ private int mRotationAnimationHint = -1;
+ private Bundle mAppVerificationBundle;
+ private IAppTransitionAnimationSpecsFuture mSpecsFuture;
+ private RemoteAnimationAdapter mRemoteAnimationAdapter;
+ private IBinder mLaunchCookie;
+ private RemoteTransition mRemoteTransition;
+ private boolean mOverrideTaskTransition;
+ private String mSplashScreenThemeResName;
+ @SplashScreen.SplashScreenStyle
+ private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+ private boolean mIsEligibleForLegacyPermissionPrompt;
+ private boolean mRemoveWithTaskOrganizer;
+ private boolean mLaunchedFromBubble;
+ private boolean mTransientLaunch;
+ private PictureInPictureParams mLaunchIntoPipParams;
+ private boolean mDismissKeyguard;
+ @BackgroundActivityStartMode
+ private int mPendingIntentCreatorBackgroundActivityStartMode =
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ private boolean mDisableStartingWindow;
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeCustomAnimation(Context context,
+ int enterResId, int exitResId) {
+ return makeCustomAnimation(context, enterResId, exitResId, 0, null, null);
+ }
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation if
+ * the animation requires a background. Set to 0 to not override the default color.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
+ int enterResId, int exitResId, int backgroundColor) {
+ return makeCustomAnimation(context, enterResId, exitResId, backgroundColor, null, null);
+ }
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param handler If <var>listener</var> is non-null this must be a valid
+ * Handler on which to dispatch the callback; otherwise it should be null.
+ * @param listener Optional OnAnimationStartedListener to find out when the
+ * requested animation has started running. If for some reason the animation
+ * is not executed, the callback will happen immediately.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static ActivityOptions makeCustomAnimation(Context context,
+ int enterResId, int exitResId, int backgroundColor, Handler handler,
+ OnAnimationStartedListener listener) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = context.getPackageName();
+ opts.mAnimationType = ANIM_CUSTOM;
+ opts.mCustomEnterResId = enterResId;
+ opts.mCustomExitResId = exitResId;
+ opts.mCustomBackgroundColor = backgroundColor;
+ opts.setOnAnimationStartedListener(handler, listener);
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param handler If <var>listener</var> is non-null this must be a valid
+ * Handler on which to dispatch the callback; otherwise it should be null.
+ * @param startedListener Optional OnAnimationStartedListener to find out when the
+ * requested animation has started running. If for some reason the animation
+ * is not executed, the callback will happen immediately.
+ * @param finishedListener Optional OnAnimationFinishedListener when the animation
+ * has finished running.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @hide
+ */
+ @TestApi
+ public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
+ int enterResId, int exitResId, int backgroundColor, @Nullable Handler handler,
+ @Nullable OnAnimationStartedListener startedListener,
+ @Nullable OnAnimationFinishedListener finishedListener) {
+ ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, backgroundColor,
+ handler, startedListener);
+ opts.setOnAnimationFinishedListener(handler, finishedListener);
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when the activity in the
+ * different task is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param handler If <var>listener</var> is non-null this must be a valid
+ * Handler on which to dispatch the callback; otherwise it should be null.
+ * @param startedListener Optional OnAnimationStartedListener to find out when the
+ * requested animation has started running. If for some reason the animation
+ * is not executed, the callback will happen immediately.
+ * @param finishedListener Optional OnAnimationFinishedListener when the animation
+ * has finished running.
+ *
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @hide
+ */
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ @TestApi
+ public static @NonNull ActivityOptions makeCustomTaskAnimation(@NonNull Context context,
+ int enterResId, int exitResId, @Nullable Handler handler,
+ @Nullable OnAnimationStartedListener startedListener,
+ @Nullable OnAnimationFinishedListener finishedListener) {
+ ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, 0,
+ handler, startedListener, finishedListener);
+ opts.mOverrideTaskTransition = true;
+ return opts;
+ }
+
+ /**
+ * Creates an ActivityOptions specifying a custom animation to run in place on an existing
+ * activity.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param animId A resource ID of the animation resource to use for
+ * the incoming activity.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when running an in-place animation.
+ * @hide
+ */
+ public static ActivityOptions makeCustomInPlaceAnimation(Context context, int animId) {
+ if (animId == 0) {
+ throw new RuntimeException("You must specify a valid animation.");
+ }
+
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = context.getPackageName();
+ opts.mAnimationType = ANIM_CUSTOM_IN_PLACE;
+ opts.mCustomInPlaceResId = animId;
+ return opts;
+ }
+
+ private void setOnAnimationStartedListener(final Handler handler,
+ final OnAnimationStartedListener listener) {
+ if (listener != null) {
+ mAnimationStartedListener = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ handler.post(new Runnable() {
+ @Override public void run() {
+ listener.onAnimationStarted(elapsedRealtime);
+ }
+ });
+ }
+ };
+ }
+ }
+
+ /**
+ * Callback for finding out when the given animation has started running.
+ * @hide
+ */
+ @TestApi
+ public interface OnAnimationStartedListener {
+ /**
+ * @param elapsedRealTime {@link SystemClock#elapsedRealTime} when animation started.
+ */
+ void onAnimationStarted(long elapsedRealTime);
+ }
+
+ private void setOnAnimationFinishedListener(final Handler handler,
+ final OnAnimationFinishedListener listener) {
+ if (listener != null) {
+ mAnimationFinishedListener = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onAnimationFinished(elapsedRealtime);
+ }
+ });
+ }
+ };
+ }
+ }
+
+ /**
+ * Callback for finding out when the given animation has drawn its last frame.
+ * @hide
+ */
+ @TestApi
+ public interface OnAnimationFinishedListener {
+ /**
+ * @param elapsedRealTime {@link SystemClock#elapsedRealTime} when animation finished.
+ */
+ void onAnimationFinished(long elapsedRealTime);
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where the new
+ * activity is scaled from a small originating area of the screen to
+ * its final full representation.
+ *
+ * <p>If the Intent this is being used with has not set its
+ * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
+ * those bounds will be filled in for you based on the initial
+ * bounds passed in here.
+ *
+ * @param source The View that the new activity is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param startX The x starting location of the new activity, relative to <var>source</var>.
+ * @param startY The y starting location of the activity, relative to <var>source</var>.
+ * @param width The initial width of the new activity.
+ * @param height The initial height of the new activity.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeScaleUpAnimation(View source,
+ int startX, int startY, int width, int height) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = source.getContext().getPackageName();
+ opts.mAnimationType = ANIM_SCALE_UP;
+ int[] pts = new int[2];
+ source.getLocationOnScreen(pts);
+ opts.mStartX = pts[0] + startX;
+ opts.mStartY = pts[1] + startY;
+ opts.mWidth = width;
+ opts.mHeight = height;
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where the new
+ * activity is revealed from a small originating area of the screen to
+ * its final full representation.
+ *
+ * @param source The View that the new activity is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param startX The x starting location of the new activity, relative to <var>source</var>.
+ * @param startY The y starting location of the activity, relative to <var>source</var>.
+ * @param width The initial width of the new activity.
+ * @param height The initial height of the new activity.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeClipRevealAnimation(View source,
+ int startX, int startY, int width, int height) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_CLIP_REVEAL;
+ int[] pts = new int[2];
+ source.getLocationOnScreen(pts);
+ opts.mStartX = pts[0] + startX;
+ opts.mStartY = pts[1] + startY;
+ opts.mWidth = width;
+ opts.mHeight = height;
+ return opts;
+ }
+
+ /**
+ * Creates an {@link ActivityOptions} object specifying an animation where the new activity
+ * is started in another user profile by calling {@link
+ * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
+ * }.
+ * @hide
+ */
+ public static ActivityOptions makeOpenCrossProfileAppsAnimation() {
+ ActivityOptions options = new ActivityOptions();
+ options.mAnimationType = ANIM_OPEN_CROSS_PROFILE_APPS;
+ return options;
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where a thumbnail
+ * is scaled from a given position to the new activity window that is
+ * being started.
+ *
+ * <p>If the Intent this is being used with has not set its
+ * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
+ * those bounds will be filled in for you based on the initial
+ * thumbnail location and size provided here.
+ *
+ * @param source The View that this thumbnail is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param thumbnail The bitmap that will be shown as the initial thumbnail
+ * of the animation.
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>.
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+ Bitmap thumbnail, int startX, int startY) {
+ return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where a thumbnail
+ * is scaled from a given position to the new activity window that is
+ * being started.
+ *
+ * @param source The View that this thumbnail is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param thumbnail The bitmap that will be shown as the initial thumbnail
+ * of the animation.
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>.
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>.
+ * @param listener Optional OnAnimationStartedListener to find out when the
+ * requested animation has started running. If for some reason the animation
+ * is not executed, the callback will happen immediately.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ private static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+ Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
+ return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
+ }
+
+ private static ActivityOptions makeThumbnailAnimation(View source,
+ Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
+ boolean scaleUp) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = source.getContext().getPackageName();
+ opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
+ opts.mThumbnail = thumbnail;
+ int[] pts = new int[2];
+ source.getLocationOnScreen(pts);
+ opts.mStartX = pts[0] + startX;
+ opts.mStartY = pts[1] + startY;
+ opts.setOnAnimationStartedListener(source.getHandler(), listener);
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where a list of activity windows and
+ * thumbnails are aspect scaled to/from a new location.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static ActivityOptions makeMultiThumbFutureAspectScaleAnimation(Context context,
+ Handler handler, IAppTransitionAnimationSpecsFuture specsFuture,
+ OnAnimationStartedListener listener, boolean scaleUp) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = context.getPackageName();
+ opts.mAnimationType = scaleUp
+ ? ANIM_THUMBNAIL_ASPECT_SCALE_UP
+ : ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+ opts.mSpecsFuture = specsFuture;
+ opts.setOnAnimationStartedListener(handler, listener);
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where the new activity
+ * window and a thumbnail is aspect-scaled to a new location.
+ *
+ * @param source The View that this thumbnail is animating to. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param thumbnail The bitmap that will be shown as the final thumbnail
+ * of the animation.
+ * @param startX The x end location of the bitmap, relative to <var>source</var>.
+ * @param startY The y end location of the bitmap, relative to <var>source</var>.
+ * @param handler If <var>listener</var> is non-null this must be a valid
+ * Handler on which to dispatch the callback; otherwise it should be null.
+ * @param listener Optional OnAnimationStartedListener to find out when the
+ * requested animation has started running. If for some reason the animation
+ * is not executed, the callback will happen immediately.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @hide
+ */
+ public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
+ Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
+ Handler handler, OnAnimationStartedListener listener) {
+ return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
+ targetWidth, targetHeight, handler, listener, false);
+ }
+
+ private static ActivityOptions makeAspectScaledThumbnailAnimation(View source, Bitmap thumbnail,
+ int startX, int startY, int targetWidth, int targetHeight,
+ Handler handler, OnAnimationStartedListener listener, boolean scaleUp) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = source.getContext().getPackageName();
+ opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_ASPECT_SCALE_UP :
+ ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+ opts.mThumbnail = thumbnail;
+ int[] pts = new int[2];
+ source.getLocationOnScreen(pts);
+ opts.mStartX = pts[0] + startX;
+ opts.mStartY = pts[1] + startY;
+ opts.mWidth = targetWidth;
+ opts.mHeight = targetHeight;
+ opts.setOnAnimationStartedListener(handler, listener);
+ return opts;
+ }
+
+ /** @hide */
+ public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
+ AppTransitionAnimationSpec[] specs, Handler handler,
+ OnAnimationStartedListener onAnimationStartedListener,
+ OnAnimationFinishedListener onAnimationFinishedListener) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = source.getContext().getPackageName();
+ opts.mAnimationType = ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+ opts.mAnimSpecs = specs;
+ opts.setOnAnimationStartedListener(handler, onAnimationStartedListener);
+ opts.setOnAnimationFinishedListener(handler, onAnimationFinishedListener);
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions to transition between Activities using cross-Activity scene
+ * animations. This method carries the position of one shared element to the started Activity.
+ * The position of <code>sharedElement</code> will be used as the epicenter for the
+ * exit Transition. The position of the shared element in the launched Activity will be the
+ * epicenter of its entering Transition.
+ *
+ * <p>This requires {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS} to be
+ * enabled on the calling Activity to cause an exit transition. The same must be in
+ * the called Activity to get an entering transition.</p>
+ * @param activity The Activity whose window contains the shared elements.
+ * @param sharedElement The View to transition to the started Activity.
+ * @param sharedElementName The shared element name as used in the target Activity. This
+ * must not be null.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @see android.transition.Transition#setEpicenterCallback(
+ * android.transition.Transition.EpicenterCallback)
+ */
+ public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+ View sharedElement, String sharedElementName) {
+ return makeSceneTransitionAnimation(activity, Pair.create(sharedElement, sharedElementName));
+ }
+
+ /**
+ * Create an ActivityOptions to transition between Activities using cross-Activity scene
+ * animations. This method carries the position of multiple shared elements to the started
+ * Activity. The position of the first element in sharedElements
+ * will be used as the epicenter for the exit Transition. The position of the associated
+ * shared element in the launched Activity will be the epicenter of its entering Transition.
+ *
+ * <p>This requires {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS} to be
+ * enabled on the calling Activity to cause an exit transition. The same must be in
+ * the called Activity to get an entering transition.</p>
+ * @param activity The Activity whose window contains the shared elements.
+ * @param sharedElements The names of the shared elements to transfer to the called
+ * Activity and their associated Views. The Views must each have
+ * a unique shared element name.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ * @see android.transition.Transition#setEpicenterCallback(
+ * android.transition.Transition.EpicenterCallback)
+ */
+ @SafeVarargs
+ public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+ Pair<View, String>... sharedElements) {
+ ActivityOptions opts = new ActivityOptions();
+ ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
+ new ActivityExitTransitionCallbacks(activity), activity.mExitTransitionListener,
+ activity.getWindow(), opts, sharedElements);
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+ return opts;
+ }
+
+ /**
+ * Call this immediately prior to startActivity to begin a shared element transition
+ * from a non-Activity. The window must support Window.FEATURE_ACTIVITY_TRANSITIONS.
+ * The exit transition will start immediately and the shared element transition will
+ * start once the launched Activity's shared element is ready.
+ * <p>
+ * When all transitions have completed and the shared element has been transfered,
+ * the window's decor View will have its visibility set to View.GONE.
+ *
+ * @hide
+ */
+ @SafeVarargs
+ public static Pair<ActivityOptions, ExitTransitionCoordinator> startSharedElementAnimation(
+ Window window, ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback,
+ Pair<View, String>... sharedElements) {
+ ActivityOptions opts = new ActivityOptions();
+ ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
+ exitCallbacks, callback, window, opts, sharedElements);
+ opts.mExitCoordinatorIndex = -1;
+ return Pair.create(opts, exit);
+ }
+
+ /**
+ * This method should be called when the
+ * {@link #startSharedElementAnimation(Window, ExitTransitionCallbacks, Pair[])}
+ * animation must be stopped and the Views reset. This can happen if there was an error
+ * from startActivity or a springboard activity and the animation should stop and reset.
+ *
+ * @hide
+ */
+ public static void stopSharedElementAnimation(Window window) {
+ final View decorView = window.getDecorView();
+ if (decorView == null) {
+ return;
+ }
+ final ExitTransitionCoordinator exit = (ExitTransitionCoordinator)
+ decorView.getTag(com.android.internal.R.id.cross_task_transition);
+ if (exit != null) {
+ exit.cancelPendingTransitions();
+ decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, null);
+ TransitionManager.endTransitions((ViewGroup) decorView);
+ exit.resetViews();
+ exit.clearState();
+ decorView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ static ExitTransitionCoordinator makeSceneTransitionAnimation(
+ ExitTransitionCallbacks exitCallbacks, SharedElementCallback callback, Window window,
+ ActivityOptions opts, Pair<View, String>[] sharedElements) {
+ if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
+ opts.mAnimationType = ANIM_DEFAULT;
+ return null;
+ }
+ opts.mAnimationType = ANIM_SCENE_TRANSITION;
+
+ ArrayList<String> names = new ArrayList<String>();
+ ArrayList<View> views = new ArrayList<View>();
+
+ if (sharedElements != null) {
+ for (int i = 0; i < sharedElements.length; i++) {
+ Pair<View, String> sharedElement = sharedElements[i];
+ String sharedElementName = sharedElement.second;
+ if (sharedElementName == null) {
+ throw new IllegalArgumentException("Shared element name must not be null");
+ }
+ names.add(sharedElementName);
+ View view = sharedElement.first;
+ if (view == null) {
+ throw new IllegalArgumentException("Shared element must not be null");
+ }
+ views.add(sharedElement.first);
+ }
+ }
+
+ ExitTransitionCoordinator exit = new ExitTransitionCoordinator(exitCallbacks, window,
+ callback, names, names, views, false);
+ opts.mTransitionReceiver = exit;
+ opts.mSharedElementNames = names;
+ opts.mIsReturning = false;
+ return exit;
+ }
+
+ /**
+ * Needed for virtual devices because they can be slow enough that the 1 second timeout
+ * triggers when it doesn't on normal devices.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void setExitTransitionTimeout(long timeoutMillis) {
+ ExitTransitionCoordinator.sMaxWaitMillis = timeoutMillis;
+ }
+
+ /** @hide */
+ static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+ ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
+ int resultCode, Intent resultData) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_SCENE_TRANSITION;
+ opts.mSharedElementNames = sharedElementNames;
+ opts.mTransitionReceiver = exitCoordinator;
+ opts.mIsReturning = true;
+ opts.mResultCode = resultCode;
+ opts.mResultData = resultData;
+ if (activity == null) {
+ opts.mExitCoordinatorIndex = -1;
+ } else {
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+ }
+ return opts;
+ }
+
+ /**
+ * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
+ * presented to the user but will instead be only available through the recents task list.
+ * In addition, the new task wil be affiliated with the launching activity's task.
+ * Affiliated tasks are grouped together in the recents task list.
+ *
+ * <p>This behavior is not supported for activities with {@link
+ * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
+ * <code>singleInstance</code> or <code>singleTask</code>.
+ */
+ public static ActivityOptions makeTaskLaunchBehind() {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_LAUNCH_TASK_BEHIND;
+ return opts;
+ }
+
+ /**
+ * Create a basic ActivityOptions that has no special animation associated with it.
+ * Other options can still be set.
+ */
+ public static ActivityOptions makeBasic() {
+ final ActivityOptions opts = new ActivityOptions();
+ return opts;
+ }
+
+ /**
+ * Create an {@link ActivityOptions} instance that lets the application control the entire
+ * animation using a {@link RemoteAnimationAdapter}.
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ @UnsupportedAppUsage
+ public static ActivityOptions makeRemoteAnimation(
+ RemoteAnimationAdapter remoteAnimationAdapter) {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
+ opts.mAnimationType = ANIM_REMOTE_ANIMATION;
+ return opts;
+ }
+
+ /**
+ * Create an {@link ActivityOptions} instance that lets the application control the entire
+ * animation using a {@link RemoteAnimationAdapter}.
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public static ActivityOptions makeRemoteAnimation(RemoteAnimationAdapter remoteAnimationAdapter,
+ RemoteTransition remoteTransition) {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
+ opts.mAnimationType = ANIM_REMOTE_ANIMATION;
+ opts.mRemoteTransition = remoteTransition;
+ return opts;
+ }
+
+ /**
+ * Create an {@link ActivityOptions} instance that lets the application control the entire
+ * transition using a {@link RemoteTransition}.
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public static ActivityOptions makeRemoteTransition(RemoteTransition remoteTransition) {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mRemoteTransition = remoteTransition;
+ return opts;
+ }
+
+ /**
+ * Creates an {@link ActivityOptions} instance that launch into picture-in-picture.
+ * This is normally used by a Host activity to start another activity that will directly enter
+ * picture-in-picture upon its creation.
+ * @param pictureInPictureParams {@link PictureInPictureParams} for launching the Activity to
+ * picture-in-picture mode.
+ */
+ @NonNull
+ public static ActivityOptions makeLaunchIntoPip(
+ @NonNull PictureInPictureParams pictureInPictureParams) {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mLaunchIntoPipParams = new PictureInPictureParams.Builder(pictureInPictureParams)
+ .setIsLaunchIntoPip(true)
+ .build();
+ return opts;
+ }
+
+ /** @hide */
+ public boolean getLaunchTaskBehind() {
+ return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
+ }
+
+ private ActivityOptions() {
+ super();
+ }
+
+ /** @hide */
+ public ActivityOptions(Bundle opts) {
+ super(opts);
+
+ mPackageName = opts.getString(KEY_PACKAGE_NAME);
+ try {
+ mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT, PendingIntent.class);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, e);
+ }
+ mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS, android.graphics.Rect.class);
+ mAnimationType = opts.getInt(KEY_ANIM_TYPE, ANIM_UNDEFINED);
+ switch (mAnimationType) {
+ case ANIM_CUSTOM:
+ mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
+ mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+ mCustomBackgroundColor = opts.getInt(KEY_ANIM_BACKGROUND_COLOR, 0);
+ mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_ANIM_START_LISTENER));
+ break;
+
+ case ANIM_CUSTOM_IN_PLACE:
+ mCustomInPlaceResId = opts.getInt(KEY_ANIM_IN_PLACE_RES_ID, 0);
+ break;
+
+ case ANIM_SCALE_UP:
+ case ANIM_CLIP_REVEAL:
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+ mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
+ mHeight = opts.getInt(KEY_ANIM_HEIGHT, 0);
+ break;
+
+ case ANIM_THUMBNAIL_SCALE_UP:
+ case ANIM_THUMBNAIL_SCALE_DOWN:
+ case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+ case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+ // Unpackage the HardwareBuffer from the parceled thumbnail
+ final HardwareBuffer buffer = opts.getParcelable(KEY_ANIM_THUMBNAIL, android.hardware.HardwareBuffer.class);
+ if (buffer != null) {
+ mThumbnail = Bitmap.wrapHardwareBuffer(buffer, null);
+ }
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+ mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
+ mHeight = opts.getInt(KEY_ANIM_HEIGHT, 0);
+ mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_ANIM_START_LISTENER));
+ break;
+
+ case ANIM_SCENE_TRANSITION:
+ mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER, android.os.ResultReceiver.class);
+ mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
+ mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
+ mResultData = opts.getParcelable(KEY_RESULT_DATA, android.content.Intent.class);
+ mResultCode = opts.getInt(KEY_RESULT_CODE);
+ mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
+ break;
+ }
+ mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
+ mShareIdentity = opts.getBoolean(KEY_SHARE_IDENTITY, false);
+ mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
+ mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
+ mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, android.window.WindowContainerToken.class);
+ mLaunchTaskDisplayAreaFeatureId = opts.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID,
+ FEATURE_UNDEFINED);
+ mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, android.window.WindowContainerToken.class);
+ mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+ mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
+ mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
+ mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
+ mPendingIntentLaunchFlags = opts.getInt(KEY_PENDING_INTENT_LAUNCH_FLAGS, 0);
+ mTaskAlwaysOnTop = opts.getBoolean(KEY_TASK_ALWAYS_ON_TOP, false);
+ mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
+ mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
+ mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false);
+ mFreezeRecentTasksReordering = opts.getBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, false);
+ mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
+ KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
+ mApplyActivityFlagsForBubbles = opts.getBoolean(
+ KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, false);
+ mApplyMultipleTaskFlagForShortcut = opts.getBoolean(
+ KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, false);
+ mApplyNoUserActionFlagForShortcut = opts.getBoolean(
+ KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT, false);
+ if (opts.containsKey(KEY_ANIM_SPECS)) {
+ Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
+ mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
+ for (int i = specs.length - 1; i >= 0; i--) {
+ mAnimSpecs[i] = (AppTransitionAnimationSpec) specs[i];
+ }
+ }
+ if (opts.containsKey(KEY_ANIMATION_FINISHED_LISTENER)) {
+ mAnimationFinishedListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_ANIMATION_FINISHED_LISTENER));
+ }
+ mSourceInfo = opts.getParcelable(KEY_SOURCE_INFO, android.app.ActivityOptions.SourceInfo.class);
+ mRotationAnimationHint = opts.getInt(KEY_ROTATION_ANIMATION_HINT, -1);
+ mAppVerificationBundle = opts.getBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE);
+ if (opts.containsKey(KEY_SPECS_FUTURE)) {
+ mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder(
+ KEY_SPECS_FUTURE));
+ }
+ mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER, android.view.RemoteAnimationAdapter.class);
+ mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE);
+ mRemoteTransition = opts.getParcelable(KEY_REMOTE_TRANSITION, android.window.RemoteTransition.class);
+ mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
+ mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME);
+ mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
+ mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
+ mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
+ mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE);
+ mLaunchIntoPipParams = opts.getParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, android.app.PictureInPictureParams.class);
+ mIsEligibleForLegacyPermissionPrompt =
+ opts.getBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE);
+ mDismissKeyguard = opts.getBoolean(KEY_DISMISS_KEYGUARD);
+ mPendingIntentCreatorBackgroundActivityStartMode = opts.getInt(
+ KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+ mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
+ }
+
+ /**
+ * Sets the bounds (window size and position) that the activity should be launched in.
+ * Rect position should be provided in pixels and in screen coordinates.
+ * Set to {@code null} to explicitly launch fullscreen.
+ * <p>
+ * <strong>NOTE:</strong> This value is ignored on devices that don't have
+ * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
+ * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
+ * @param screenSpacePixelRect launch bounds or {@code null} for fullscreen
+ * @return {@code this} {@link ActivityOptions} instance
+ */
+ public ActivityOptions setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
+ mLaunchBounds = screenSpacePixelRect != null ? new Rect(screenSpacePixelRect) : null;
+ return this;
+ }
+
+ /** @hide */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the bounds that should be used to launch the activity.
+ * @see #setLaunchBounds(Rect)
+ * @return Bounds used to launch the activity.
+ */
+ @Nullable
+ public Rect getLaunchBounds() {
+ return mLaunchBounds;
+ }
+
+ /** @hide */
+ public int getAnimationType() {
+ return mAnimationType;
+ }
+
+ /** @hide */
+ public int getCustomEnterResId() {
+ return mCustomEnterResId;
+ }
+
+ /** @hide */
+ public int getCustomExitResId() {
+ return mCustomExitResId;
+ }
+
+ /** @hide */
+ public int getCustomInPlaceResId() {
+ return mCustomInPlaceResId;
+ }
+
+ /** @hide */
+ public int getCustomBackgroundColor() {
+ return mCustomBackgroundColor;
+ }
+
+ /**
+ * The thumbnail is copied into a hardware bitmap when it is bundled and sent to the system, so
+ * it should always be backed by a HardwareBuffer on the other end.
+ *
+ * @hide
+ */
+ public HardwareBuffer getThumbnail() {
+ return mThumbnail != null ? mThumbnail.getHardwareBuffer() : null;
+ }
+
+ /** @hide */
+ public int getStartX() {
+ return mStartX;
+ }
+
+ /** @hide */
+ public int getStartY() {
+ return mStartY;
+ }
+
+ /** @hide */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /** @hide */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /** @hide */
+ public IRemoteCallback getAnimationStartedListener() {
+ return mAnimationStartedListener;
+ }
+
+ /** @hide */
+ public IRemoteCallback getAnimationFinishedListener() {
+ return mAnimationFinishedListener;
+ }
+
+ /** @hide */
+ public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
+
+ /** @hide */
+ public void abort() {
+ if (mAnimationStartedListener != null) {
+ try {
+ mAnimationStartedListener.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @hide */
+ public boolean isReturning() {
+ return mIsReturning;
+ }
+
+ /**
+ * Returns whether or not the ActivityOptions was created with
+ * {@link #startSharedElementAnimation(Window, Pair[])}.
+ *
+ * @hide
+ */
+ boolean isCrossTask() {
+ return mExitCoordinatorIndex < 0;
+ }
+
+ /** @hide */
+ public ArrayList<String> getSharedElementNames() {
+ return mSharedElementNames;
+ }
+
+ /** @hide */
+ public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
+
+ /** @hide */
+ public int getResultCode() { return mResultCode; }
+
+ /** @hide */
+ public Intent getResultData() { return mResultData; }
+
+ /** @hide */
+ public PendingIntent getUsageTimeReport() {
+ return mUsageTimeReport;
+ }
+
+ /** @hide */
+ public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; }
+
+ /** @hide */
+ public IAppTransitionAnimationSpecsFuture getSpecsFuture() {
+ return mSpecsFuture;
+ }
+
+ /** @hide */
+ public RemoteAnimationAdapter getRemoteAnimationAdapter() {
+ return mRemoteAnimationAdapter;
+ }
+
+ /** @hide */
+ public void setRemoteAnimationAdapter(RemoteAnimationAdapter remoteAnimationAdapter) {
+ mRemoteAnimationAdapter = remoteAnimationAdapter;
+ }
+
+ /** @hide */
+ public RemoteTransition getRemoteTransition() {
+ return mRemoteTransition;
+ }
+
+ /** @hide */
+ public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+ mRemoteTransition = remoteTransition;
+ }
+
+ /** @hide */
+ public static ActivityOptions fromBundle(Bundle bOptions) {
+ return bOptions != null ? new ActivityOptions(bOptions) : null;
+ }
+
+ /** @hide */
+ public static void abort(ActivityOptions options) {
+ if (options != null) {
+ options.abort();
+ }
+ }
+
+ /**
+ * Gets whether the activity is to be launched into LockTask mode.
+ * @return {@code true} if the activity is to be launched into LockTask mode.
+ * @see Activity#startLockTask()
+ * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+ */
+ public boolean getLockTaskMode() {
+ return mLockTaskMode;
+ }
+
+ /**
+ * Returns whether the launching app has opted-in to sharing its identity with the launched
+ * activity.
+ *
+ * @return {@code true} if the launching app has opted-in to sharing its identity
+ *
+ * @see #setShareIdentityEnabled(boolean)
+ * @see Activity#getLaunchedFromUid()
+ * @see Activity#getLaunchedFromPackage()
+ */
+ public boolean isShareIdentityEnabled() {
+ return mShareIdentity;
+ }
+
+ /**
+ * Gets whether the activity want to be launched as other theme for the splash screen.
+ * @hide
+ */
+ @Nullable
+ public String getSplashScreenThemeResName() {
+ return mSplashScreenThemeResName;
+ }
+
+ /**
+ * Gets the style can be used for cold-launching an activity.
+ * @see #setSplashScreenStyle(int)
+ */
+ public @SplashScreen.SplashScreenStyle int getSplashScreenStyle() {
+ return mSplashScreenStyle;
+ }
+
+ /**
+ * Sets the preferred splash screen style of the opening activities. This only applies if the
+ * Activity or Process is not yet created.
+ * @param style Can be either {@link SplashScreen#SPLASH_SCREEN_STYLE_ICON} or
+ * {@link SplashScreen#SPLASH_SCREEN_STYLE_SOLID_COLOR}
+ */
+ @NonNull
+ public ActivityOptions setSplashScreenStyle(@SplashScreen.SplashScreenStyle int style) {
+ if (style == SplashScreen.SPLASH_SCREEN_STYLE_ICON
+ || style == SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) {
+ mSplashScreenStyle = style;
+ }
+ return this;
+ }
+
+ /**
+ * Whether the activity is eligible to show a legacy permission prompt
+ * @hide
+ */
+ @TestApi
+ public boolean isEligibleForLegacyPermissionPrompt() {
+ return mIsEligibleForLegacyPermissionPrompt;
+ }
+
+ /**
+ * Sets whether the activity is eligible to show a legacy permission prompt
+ * @hide
+ */
+ @TestApi
+ public void setEligibleForLegacyPermissionPrompt(boolean eligible) {
+ mIsEligibleForLegacyPermissionPrompt = eligible;
+ }
+
+ /**
+ * Sets whether the activity is to be launched into LockTask mode.
+ *
+ * Use this option to start an activity in LockTask mode. Note that only apps permitted by
+ * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if
+ * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns
+ * {@code false} for the package of the target activity, a {@link SecurityException} will be
+ * thrown during {@link Context#startActivity(Intent, Bundle)}. This method doesn't affect
+ * activities that are already running — relaunch the activity to run in lock task mode.
+ *
+ * Defaults to {@code false} if not set.
+ *
+ * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode.
+ * @return {@code this} {@link ActivityOptions} instance.
+ * @see Activity#startLockTask()
+ * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+ */
+ public ActivityOptions setLockTaskEnabled(boolean lockTaskMode) {
+ mLockTaskMode = lockTaskMode;
+ return this;
+ }
+
+ /**
+ * Sets whether the identity of the launching app should be shared with the activity.
+ *
+ * <p>Use this option when starting an activity that needs to know the identity of the
+ * launching app; with this set to {@code true}, the activity will have access to the launching
+ * app's package name and uid.
+ *
+ * <p>Defaults to {@code false} if not set.
+ *
+ * <p>Note, even if the launching app does not explicitly enable sharing of its identity, if
+ * the activity is started with {@code Activity#startActivityForResult}, then {@link
+ * Activity#getCallingPackage()} will still return the launching app's package name to
+ * allow validation of the result's recipient. Also, an activity running within a package
+ * signed by the same key used to sign the platform (some system apps such as Settings will
+ * be signed with the platform's key) will have access to the launching app's identity.
+ *
+ * @param shareIdentity whether the launching app's identity should be shared with the activity
+ * @return {@code this} {@link ActivityOptions} instance.
+ * @see Activity#getLaunchedFromPackage()
+ * @see Activity#getLaunchedFromUid()
+ */
+ @NonNull
+ public ActivityOptions setShareIdentityEnabled(boolean shareIdentity) {
+ mShareIdentity = shareIdentity;
+ return this;
+ }
+
+ /**
+ * Gets the id of the display where activity should be launched.
+ * @return The id of the display where activity should be launched,
+ * {@link android.view.Display#INVALID_DISPLAY} if not set.
+ * @see #setLaunchDisplayId(int)
+ */
+ public int getLaunchDisplayId() {
+ return mLaunchDisplayId;
+ }
+
+ /**
+ * Sets the id of the display where the activity should be launched.
+ * An app can launch activities on public displays or displays where the app already has
+ * activities. Otherwise, trying to launch on a private display or providing an invalid display
+ * id will result in an exception.
+ * <p>
+ * Setting launch display id will be ignored on devices that don't have
+ * {@link android.content.pm.PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}.
+ * @param launchDisplayId The id of the display where the activity should be launched.
+ * @return {@code this} {@link ActivityOptions} instance.
+ */
+ public ActivityOptions setLaunchDisplayId(int launchDisplayId) {
+ mLaunchDisplayId = launchDisplayId;
+ return this;
+ }
+
+ /** @hide */
+ public int getCallerDisplayId() {
+ return mCallerDisplayId;
+ }
+
+ /** @hide */
+ public ActivityOptions setCallerDisplayId(int callerDisplayId) {
+ mCallerDisplayId = callerDisplayId;
+ return this;
+ }
+
+ /** @hide */
+ public WindowContainerToken getLaunchTaskDisplayArea() {
+ return mLaunchTaskDisplayArea;
+ }
+
+ /** @hide */
+ public ActivityOptions setLaunchTaskDisplayArea(
+ WindowContainerToken windowContainerToken) {
+ mLaunchTaskDisplayArea = windowContainerToken;
+ return this;
+ }
+
+ /** @hide */
+ public int getLaunchTaskDisplayAreaFeatureId() {
+ return mLaunchTaskDisplayAreaFeatureId;
+ }
+
+ /**
+ * Sets the TaskDisplayArea feature Id the activity should launch into.
+ * Note: It is possible to have TaskDisplayAreas with the same featureId on multiple displays.
+ * If launch display id is not specified, the TaskDisplayArea on the default display will be
+ * used.
+ * @hide
+ */
+ @TestApi
+ public void setLaunchTaskDisplayAreaFeatureId(int launchTaskDisplayAreaFeatureId) {
+ mLaunchTaskDisplayAreaFeatureId = launchTaskDisplayAreaFeatureId;
+ }
+
+ /** @hide */
+ public WindowContainerToken getLaunchRootTask() {
+ return mLaunchRootTask;
+ }
+
+ /** @hide */
+ public ActivityOptions setLaunchRootTask(WindowContainerToken windowContainerToken) {
+ mLaunchRootTask = windowContainerToken;
+ return this;
+ }
+
+ /** @hide */
+ public IBinder getLaunchTaskFragmentToken() {
+ return mLaunchTaskFragmentToken;
+ }
+
+ /** @hide */
+ public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) {
+ mLaunchTaskFragmentToken = taskFragmentToken;
+ return this;
+ }
+
+ /** @hide */
+ public int getLaunchWindowingMode() {
+ return mLaunchWindowingMode;
+ }
+
+ /**
+ * Sets the windowing mode the activity should launch into.
+ * @hide
+ */
+ @TestApi
+ public void setLaunchWindowingMode(int windowingMode) {
+ mLaunchWindowingMode = windowingMode;
+ }
+
+ /**
+ * @return {@link PictureInPictureParams} used to launch into PiP mode.
+ * @hide
+ */
+ public PictureInPictureParams getLaunchIntoPipParams() {
+ return mLaunchIntoPipParams;
+ }
+
+ /**
+ * @return {@code true} if this instance is used to launch into PiP mode.
+ * @hide
+ */
+ public boolean isLaunchIntoPip() {
+ return mLaunchIntoPipParams != null
+ && mLaunchIntoPipParams.isLaunchIntoPip();
+ }
+
+ /** @hide */
+ public int getLaunchActivityType() {
+ return mLaunchActivityType;
+ }
+
+ /** @hide */
+ @TestApi
+ public void setLaunchActivityType(int activityType) {
+ mLaunchActivityType = activityType;
+ }
+
+ /**
+ * Sets the task the activity will be launched in.
+ * @hide
+ */
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ @SystemApi
+ public void setLaunchTaskId(int taskId) {
+ mLaunchTaskId = taskId;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public int getLaunchTaskId() {
+ return mLaunchTaskId;
+ }
+
+ /**
+ * Sets whether recents disable showing starting window when activity launch.
+ * @hide
+ */
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ public void setDisableStartingWindow(boolean disable) {
+ mDisableStartingWindow = disable;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getDisableStartingWindow() {
+ return mDisableStartingWindow;
+ }
+
+ /**
+ * Specifies intent flags to be applied for any activity started from a PendingIntent.
+ *
+ * @hide
+ */
+ public void setPendingIntentLaunchFlags(@android.content.Intent.Flags int flags) {
+ mPendingIntentLaunchFlags = flags;
+ }
+
+ /**
+ * @hide
+ */
+ public int getPendingIntentLaunchFlags() {
+ // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217
+ return mPendingIntentLaunchFlags &
+ (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND);
+ }
+
+ /**
+ * Set's whether the task for the activity launched with this option should always be on top.
+ * @hide
+ */
+ @TestApi
+ public void setTaskAlwaysOnTop(boolean alwaysOnTop) {
+ mTaskAlwaysOnTop = alwaysOnTop;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getTaskAlwaysOnTop() {
+ return mTaskAlwaysOnTop;
+ }
+
+ /**
+ * Set's whether the activity launched with this option should be a task overlay. That is the
+ * activity will always be the top activity of the task.
+ * @param canResume {@code false} if the task will also not be moved to the front of the stack.
+ * @hide
+ */
+ @TestApi
+ public void setTaskOverlay(boolean taskOverlay, boolean canResume) {
+ mTaskOverlay = taskOverlay;
+ mTaskOverlayCanResume = canResume;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getTaskOverlay() {
+ return mTaskOverlay;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean canTaskOverlayResume() {
+ return mTaskOverlayCanResume;
+ }
+
+ /**
+ * Sets whether the activity launched should not cause the activity stack it is contained in to
+ * be moved to the front as a part of launching.
+ *
+ * @hide
+ */
+ public void setAvoidMoveToFront() {
+ mAvoidMoveToFront = true;
+ }
+
+ /**
+ * @return whether the activity launch should prevent moving the associated activity stack to
+ * the front.
+ * @hide
+ */
+ public boolean getAvoidMoveToFront() {
+ return mAvoidMoveToFront;
+ }
+
+ /**
+ * Sets whether the launch of this activity should freeze the recent task list reordering until
+ * the next user interaction or timeout. This flag is only applied when starting an activity
+ * in recents.
+ * @hide
+ */
+ public void setFreezeRecentTasksReordering() {
+ mFreezeRecentTasksReordering = true;
+ }
+
+ /**
+ * @return whether the launch of this activity should freeze the recent task list reordering
+ * @hide
+ */
+ public boolean freezeRecentTasksReordering() {
+ return mFreezeRecentTasksReordering;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+ // Remove this method after @UnsupportedAppUsage can be removed.
+ }
+
+ /** @hide */
+ public void setDisallowEnterPictureInPictureWhileLaunching(boolean disallow) {
+ mDisallowEnterPictureInPictureWhileLaunching = disallow;
+ }
+
+ /** @hide */
+ public boolean disallowEnterPictureInPictureWhileLaunching() {
+ return mDisallowEnterPictureInPictureWhileLaunching;
+ }
+
+ /** @hide */
+ public void setApplyActivityFlagsForBubbles(boolean apply) {
+ mApplyActivityFlagsForBubbles = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyActivityFlagsForBubbles() {
+ return mApplyActivityFlagsForBubbles;
+ }
+
+ /** @hide */
+ public void setApplyMultipleTaskFlagForShortcut(boolean apply) {
+ mApplyMultipleTaskFlagForShortcut = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyMultipleTaskFlagForShortcut() {
+ return mApplyMultipleTaskFlagForShortcut;
+ }
+
+ /** @hide */
+ public void setApplyNoUserActionFlagForShortcut(boolean apply) {
+ mApplyNoUserActionFlagForShortcut = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyNoUserActionFlagForShortcut() {
+ return mApplyNoUserActionFlagForShortcut;
+ }
+
+ /**
+ * Sets a launch cookie that can be used to track the activity and task that are launch as a
+ * result of this option. If the launched activity is a trampoline that starts another activity
+ * immediately, the cookie will be transferred to the next activity.
+ *
+ * @hide
+ */
+ public void setLaunchCookie(IBinder launchCookie) {
+ mLaunchCookie = launchCookie;
+ }
+
+ /**
+ * @return The launch tracking cookie if set or {@code null} otherwise.
+ *
+ * @hide
+ */
+ public IBinder getLaunchCookie() {
+ return mLaunchCookie;
+ }
+
+
+ /** @hide */
+ public boolean getOverrideTaskTransition() {
+ return mOverrideTaskTransition;
+ }
+
+ /**
+ * Sets whether to remove the task when TaskOrganizer, which is managing it, is destroyed.
+ * @hide
+ */
+ public void setRemoveWithTaskOrganizer(boolean remove) {
+ mRemoveWithTaskOrganizer = remove;
+ }
+
+ /**
+ * @return whether to remove the task when TaskOrganizer, which is managing it, is destroyed.
+ * @hide
+ */
+ public boolean getRemoveWithTaskOranizer() {
+ return mRemoveWithTaskOrganizer;
+ }
+
+ /**
+ * Sets whether this activity is launched from a bubble.
+ * @hide
+ */
+ @TestApi
+ public void setLaunchedFromBubble(boolean fromBubble) {
+ mLaunchedFromBubble = fromBubble;
+ }
+
+ /**
+ * @return whether the activity was launched from a bubble.
+ * @hide
+ */
+ public boolean getLaunchedFromBubble() {
+ return mLaunchedFromBubble;
+ }
+
+ /**
+ * Sets whether the activity launch is part of a transient operation. If it is, it will not
+ * cause lifecycle changes in existing activities even if it were to occlude them (ie. other
+ * activities occluded by this one will not be paused or stopped until the launch is committed).
+ * As a consequence, it will start immediately since it doesn't need to wait for other
+ * lifecycles to evolve. Current user is recents.
+ * @hide
+ */
+ public ActivityOptions setTransientLaunch() {
+ mTransientLaunch = true;
+ return this;
+ }
+
+ /**
+ * @see #setTransientLaunch()
+ * @return whether the activity launch is part of a transient operation.
+ * @hide
+ */
+ public boolean getTransientLaunch() {
+ return mTransientLaunch;
+ }
+
+ /**
+ * Sets whether the keyguard should go away when this activity launches.
+ *
+ * @see Activity#setShowWhenLocked(boolean)
+ * @see android.R.attr#showWhenLocked
+ * @hide
+ */
+ @RequiresPermission(CONTROL_KEYGUARD)
+ public void setDismissKeyguard() {
+ mDismissKeyguard = true;
+ }
+
+ /**
+ * @see #setDismissKeyguard()
+ * @return whether the insecure keyguard should go away when the activity launches.
+ * @hide
+ */
+ public boolean getDismissKeyguard() {
+ return mDismissKeyguard;
+ }
+
+ /**
+ * Sets background activity launch logic won't use pending intent creator foreground state.
+ *
+ * @hide
+ * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead
+ */
+ @Deprecated
+ public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) {
+ mPendingIntentCreatorBackgroundActivityStartMode = ignore
+ ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ return this;
+ }
+
+ /**
+ * Allow a {@link PendingIntent} to use the privilege of its creator to start background
+ * activities.
+ *
+ * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
+ * @throws IllegalArgumentException is the value is not a valid
+ * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
+ */
+ @NonNull
+ public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
+ @BackgroundActivityStartMode int mode) {
+ mPendingIntentCreatorBackgroundActivityStartMode = mode;
+ return this;
+ }
+
+ /**
+ * Returns the mode to start background activities granted by the creator of the
+ * {@link PendingIntent}.
+ *
+ * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+ */
+ public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
+ return mPendingIntentCreatorBackgroundActivityStartMode;
+ }
+
+ /**
+ * Update the current values in this ActivityOptions from those supplied
+ * in <var>otherOptions</var>. Any values
+ * defined in <var>otherOptions</var> replace those in the base options.
+ */
+ public void update(ActivityOptions otherOptions) {
+ if (otherOptions.mPackageName != null) {
+ mPackageName = otherOptions.mPackageName;
+ }
+ mUsageTimeReport = otherOptions.mUsageTimeReport;
+ mTransitionReceiver = null;
+ mSharedElementNames = null;
+ mIsReturning = false;
+ mResultData = null;
+ mResultCode = 0;
+ mExitCoordinatorIndex = 0;
+ mAnimationType = otherOptions.mAnimationType;
+ switch (otherOptions.mAnimationType) {
+ case ANIM_CUSTOM:
+ mCustomEnterResId = otherOptions.mCustomEnterResId;
+ mCustomExitResId = otherOptions.mCustomExitResId;
+ mCustomBackgroundColor = otherOptions.mCustomBackgroundColor;
+ mThumbnail = null;
+ if (mAnimationStartedListener != null) {
+ try {
+ mAnimationStartedListener.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+ mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+ break;
+ case ANIM_CUSTOM_IN_PLACE:
+ mCustomInPlaceResId = otherOptions.mCustomInPlaceResId;
+ break;
+ case ANIM_SCALE_UP:
+ mStartX = otherOptions.mStartX;
+ mStartY = otherOptions.mStartY;
+ mWidth = otherOptions.mWidth;
+ mHeight = otherOptions.mHeight;
+ if (mAnimationStartedListener != null) {
+ try {
+ mAnimationStartedListener.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+ mAnimationStartedListener = null;
+ break;
+ case ANIM_THUMBNAIL_SCALE_UP:
+ case ANIM_THUMBNAIL_SCALE_DOWN:
+ case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+ case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+ mThumbnail = otherOptions.mThumbnail;
+ mStartX = otherOptions.mStartX;
+ mStartY = otherOptions.mStartY;
+ mWidth = otherOptions.mWidth;
+ mHeight = otherOptions.mHeight;
+ if (mAnimationStartedListener != null) {
+ try {
+ mAnimationStartedListener.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+ mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+ break;
+ case ANIM_SCENE_TRANSITION:
+ mTransitionReceiver = otherOptions.mTransitionReceiver;
+ mSharedElementNames = otherOptions.mSharedElementNames;
+ mIsReturning = otherOptions.mIsReturning;
+ mThumbnail = null;
+ mAnimationStartedListener = null;
+ mResultData = otherOptions.mResultData;
+ mResultCode = otherOptions.mResultCode;
+ mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
+ break;
+ }
+ mLockTaskMode = otherOptions.mLockTaskMode;
+ mShareIdentity = otherOptions.mShareIdentity;
+ mAnimSpecs = otherOptions.mAnimSpecs;
+ mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
+ mSpecsFuture = otherOptions.mSpecsFuture;
+ mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
+ mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
+ mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt;
+ }
+
+ /**
+ * Returns the created options as a Bundle, which can be passed to
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
+ * Context.startActivity(Intent, Bundle)} and related methods.
+ * Note that the returned Bundle is still owned by the ActivityOptions
+ * object; you must not modify it, but can supply it to the startActivity
+ * methods that take an options Bundle.
+ */
+ @Override
+ public Bundle toBundle() {
+ Bundle b = super.toBundle();
+ if (mPackageName != null) {
+ b.putString(KEY_PACKAGE_NAME, mPackageName);
+ }
+ if (mLaunchBounds != null) {
+ b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds);
+ }
+ if (mAnimationType != ANIM_UNDEFINED) {
+ b.putInt(KEY_ANIM_TYPE, mAnimationType);
+ }
+ if (mUsageTimeReport != null) {
+ b.putParcelable(KEY_USAGE_TIME_REPORT, mUsageTimeReport);
+ }
+ switch (mAnimationType) {
+ case ANIM_CUSTOM:
+ b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
+ b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+ b.putInt(KEY_ANIM_BACKGROUND_COLOR, mCustomBackgroundColor);
+ b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+ != null ? mAnimationStartedListener.asBinder() : null);
+ break;
+ case ANIM_CUSTOM_IN_PLACE:
+ b.putInt(KEY_ANIM_IN_PLACE_RES_ID, mCustomInPlaceResId);
+ break;
+ case ANIM_SCALE_UP:
+ case ANIM_CLIP_REVEAL:
+ b.putInt(KEY_ANIM_START_X, mStartX);
+ b.putInt(KEY_ANIM_START_Y, mStartY);
+ b.putInt(KEY_ANIM_WIDTH, mWidth);
+ b.putInt(KEY_ANIM_HEIGHT, mHeight);
+ break;
+ case ANIM_THUMBNAIL_SCALE_UP:
+ case ANIM_THUMBNAIL_SCALE_DOWN:
+ case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+ case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+ // Once we parcel the thumbnail for transfering over to the system, create a copy of
+ // the bitmap to a hardware bitmap and pass through the HardwareBuffer
+ if (mThumbnail != null) {
+ final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, false /* isMutable */);
+ if (hwBitmap != null) {
+ b.putParcelable(KEY_ANIM_THUMBNAIL, hwBitmap.getHardwareBuffer());
+ } else {
+ Slog.w(TAG, "Failed to copy thumbnail");
+ }
+ }
+ b.putInt(KEY_ANIM_START_X, mStartX);
+ b.putInt(KEY_ANIM_START_Y, mStartY);
+ b.putInt(KEY_ANIM_WIDTH, mWidth);
+ b.putInt(KEY_ANIM_HEIGHT, mHeight);
+ b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+ != null ? mAnimationStartedListener.asBinder() : null);
+ break;
+ case ANIM_SCENE_TRANSITION:
+ if (mTransitionReceiver != null) {
+ b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
+ }
+ b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
+ b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
+ b.putParcelable(KEY_RESULT_DATA, mResultData);
+ b.putInt(KEY_RESULT_CODE, mResultCode);
+ b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
+ break;
+ }
+ if (mLockTaskMode) {
+ b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
+ }
+ if (mShareIdentity) {
+ b.putBoolean(KEY_SHARE_IDENTITY, mShareIdentity);
+ }
+ if (mLaunchDisplayId != INVALID_DISPLAY) {
+ b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
+ }
+ if (mCallerDisplayId != INVALID_DISPLAY) {
+ b.putInt(KEY_CALLER_DISPLAY_ID, mCallerDisplayId);
+ }
+ if (mLaunchTaskDisplayArea != null) {
+ b.putParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, mLaunchTaskDisplayArea);
+ }
+ if (mLaunchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ b.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mLaunchTaskDisplayAreaFeatureId);
+ }
+ if (mLaunchRootTask != null) {
+ b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
+ }
+ if (mLaunchTaskFragmentToken != null) {
+ b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken);
+ }
+ if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
+ b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
+ }
+ if (mLaunchActivityType != ACTIVITY_TYPE_UNDEFINED) {
+ b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
+ }
+ if (mLaunchTaskId != -1) {
+ b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
+ }
+ if (mPendingIntentLaunchFlags != 0) {
+ b.putInt(KEY_PENDING_INTENT_LAUNCH_FLAGS, mPendingIntentLaunchFlags);
+ }
+ if (mTaskAlwaysOnTop) {
+ b.putBoolean(KEY_TASK_ALWAYS_ON_TOP, mTaskAlwaysOnTop);
+ }
+ if (mTaskOverlay) {
+ b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
+ }
+ if (mTaskOverlayCanResume) {
+ b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
+ }
+ if (mAvoidMoveToFront) {
+ b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront);
+ }
+ if (mFreezeRecentTasksReordering) {
+ b.putBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, mFreezeRecentTasksReordering);
+ }
+ if (mDisallowEnterPictureInPictureWhileLaunching) {
+ b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
+ mDisallowEnterPictureInPictureWhileLaunching);
+ }
+ if (mApplyActivityFlagsForBubbles) {
+ b.putBoolean(KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, mApplyActivityFlagsForBubbles);
+ }
+ if (mApplyMultipleTaskFlagForShortcut) {
+ b.putBoolean(KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT,
+ mApplyMultipleTaskFlagForShortcut);
+ }
+ if (mApplyNoUserActionFlagForShortcut) {
+ b.putBoolean(KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT, true);
+ }
+ if (mAnimSpecs != null) {
+ b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
+ }
+ if (mAnimationFinishedListener != null) {
+ b.putBinder(KEY_ANIMATION_FINISHED_LISTENER, mAnimationFinishedListener.asBinder());
+ }
+ if (mSpecsFuture != null) {
+ b.putBinder(KEY_SPECS_FUTURE, mSpecsFuture.asBinder());
+ }
+ if (mSourceInfo != null) {
+ b.putParcelable(KEY_SOURCE_INFO, mSourceInfo);
+ }
+ if (mRotationAnimationHint != -1) {
+ b.putInt(KEY_ROTATION_ANIMATION_HINT, mRotationAnimationHint);
+ }
+ if (mAppVerificationBundle != null) {
+ b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle);
+ }
+ if (mRemoteAnimationAdapter != null) {
+ b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter);
+ }
+ if (mLaunchCookie != null) {
+ b.putBinder(KEY_LAUNCH_COOKIE, mLaunchCookie);
+ }
+ if (mRemoteTransition != null) {
+ b.putParcelable(KEY_REMOTE_TRANSITION, mRemoteTransition);
+ }
+ if (mOverrideTaskTransition) {
+ b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
+ }
+ if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) {
+ b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName);
+ }
+ if (mRemoveWithTaskOrganizer) {
+ b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer);
+ }
+ if (mLaunchedFromBubble) {
+ b.putBoolean(KEY_LAUNCHED_FROM_BUBBLE, mLaunchedFromBubble);
+ }
+ if (mTransientLaunch) {
+ b.putBoolean(KEY_TRANSIENT_LAUNCH, mTransientLaunch);
+ }
+ if (mSplashScreenStyle != 0) {
+ b.putInt(KEY_SPLASH_SCREEN_STYLE, mSplashScreenStyle);
+ }
+ if (mLaunchIntoPipParams != null) {
+ b.putParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, mLaunchIntoPipParams);
+ }
+ if (mIsEligibleForLegacyPermissionPrompt) {
+ b.putBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE,
+ mIsEligibleForLegacyPermissionPrompt);
+ }
+ if (mDismissKeyguard) {
+ b.putBoolean(KEY_DISMISS_KEYGUARD, mDismissKeyguard);
+ }
+ if (mPendingIntentCreatorBackgroundActivityStartMode
+ != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ b.putInt(KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
+ mPendingIntentCreatorBackgroundActivityStartMode);
+ }
+ if (mDisableStartingWindow) {
+ b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
+ }
+ return b;
+ }
+
+ /**
+ * Ask the system track that time the user spends in the app being launched, and
+ * report it back once done. The report will be sent to the given receiver, with
+ * the extras {@link #EXTRA_USAGE_TIME_REPORT} and {@link #EXTRA_USAGE_TIME_REPORT_PACKAGES}
+ * filled in.
+ *
+ * <p>The time interval tracked is from launching this activity until the user leaves
+ * that activity's flow. They are considered to stay in the flow as long as
+ * new activities are being launched or returned to from the original flow,
+ * even if this crosses package or task boundaries. For example, if the originator
+ * starts an activity to view an image, and while there the user selects to share,
+ * which launches their email app in a new task, and they complete the share, the
+ * time during that entire operation will be included until they finally hit back from
+ * the original image viewer activity.</p>
+ *
+ * <p>The user is considered to complete a flow once they switch to another
+ * activity that is not part of the tracked flow. This may happen, for example, by
+ * using the notification shade, launcher, or recents to launch or switch to another
+ * app. Simply going in to these navigation elements does not break the flow (although
+ * the launcher and recents stops time tracking of the session); it is the act of
+ * going somewhere else that completes the tracking.</p>
+ *
+ * @param receiver A broadcast receiver that willl receive the report.
+ */
+ public void requestUsageTimeReport(PendingIntent receiver) {
+ mUsageTimeReport = receiver;
+ }
+
+ /**
+ * Returns the launch source information set by {@link #setSourceInfo}.
+ * @hide
+ */
+ public @Nullable SourceInfo getSourceInfo() {
+ return mSourceInfo;
+ }
+
+ /**
+ * Sets the source information of the launch event.
+ *
+ * @param type The type of the startup source.
+ * @param uptimeMillis The event time of startup source in milliseconds since boot, not
+ * including sleep (e.g. from {@link android.view.MotionEvent#getEventTime}
+ * or {@link android.os.SystemClock#uptimeMillis}).
+ * @see SourceInfo
+ * @hide
+ */
+ public void setSourceInfo(@SourceInfo.SourceType int type, long uptimeMillis) {
+ mSourceInfo = new SourceInfo(type, uptimeMillis);
+ }
+
+ /**
+ * Return the filtered options only meant to be seen by the target activity itself
+ * @hide
+ */
+ public ActivityOptions forTargetActivity() {
+ if (mAnimationType == ANIM_SCENE_TRANSITION) {
+ final ActivityOptions result = new ActivityOptions();
+ result.update(this);
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the rotation animation set by {@link setRotationAnimationHint} or -1
+ * if unspecified.
+ * @hide
+ */
+ public int getRotationAnimationHint() {
+ return mRotationAnimationHint;
+ }
+
+
+ /**
+ * Set a rotation animation to be used if launching the activity
+ * triggers an orientation change, or -1 to clear. See
+ * {@link android.view.WindowManager.LayoutParams} for rotation
+ * animation values.
+ * @hide
+ */
+ public void setRotationAnimationHint(int hint) {
+ mRotationAnimationHint = hint;
+ }
+
+ /**
+ * Pop the extra verification bundle for the installer.
+ * This removes the bundle from the ActivityOptions to make sure the installer bundle
+ * is only available once.
+ * @hide
+ */
+ public Bundle popAppVerificationBundle() {
+ Bundle out = mAppVerificationBundle;
+ mAppVerificationBundle = null;
+ return out;
+ }
+
+ /**
+ * Set the {@link Bundle} that is provided to the app installer for additional verification
+ * if the call to {@link Context#startActivity} results in an app being installed.
+ *
+ * This Bundle is not provided to any other app besides the installer.
+ */
+ public ActivityOptions setAppVerificationBundle(Bundle bundle) {
+ mAppVerificationBundle = bundle;
+ return this;
+
+ }
+
+ /**
+ * Sets the mode for allowing or denying the senders privileges to start background activities
+ * to the PendingIntent.
+ *
+ * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent,
+ * PendingIntent.OnFinished, Handler, String, Bundle)} or similar
+ * methods. A privileged sender of a PendingIntent should only grant
+ * {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a trusted source
+ * and/or executed on behalf the user.
+ */
+ public @NonNull ActivityOptions setPendingIntentBackgroundActivityStartMode(
+ @BackgroundActivityStartMode int state) {
+ super.setPendingIntentBackgroundActivityStartMode(state);
+ return this;
+ }
+
+ /**
+ * Get the mode for allowing or denying the senders privileges to start background activities
+ * to the PendingIntent.
+ *
+ * @see #setPendingIntentBackgroundActivityStartMode(int)
+ */
+ public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
+ return super.getPendingIntentBackgroundActivityStartMode();
+ }
+
+ /**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ *
+ * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+ * of states
+ */
+ @Override
+ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller can start
+ * background activities.
+ *
+ * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+ * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+ * not match the actual behavior if the value was not explicitly set.
+ */
+ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return super.isPendingIntentBackgroundActivityLaunchAllowed();
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return "ActivityOptions(" + hashCode() + "), mPackageName=" + mPackageName
+ + ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY="
+ + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight + ", mLaunchDisplayId="
+ + mLaunchDisplayId;
+ }
+
+ /**
+ * The information about the source of activity launch. E.g. describe an activity is launched
+ * from launcher by receiving a motion event with a timestamp.
+ * @hide
+ */
+ public static class SourceInfo implements Parcelable {
+ /** Launched from launcher. */
+ public static final int TYPE_LAUNCHER = 1;
+ /** Launched from notification. */
+ public static final int TYPE_NOTIFICATION = 2;
+ /** Launched from lockscreen, including notification while the device is locked. */
+ public static final int TYPE_LOCKSCREEN = 3;
+ /** Launched from recents gesture handler. */
+ public static final int TYPE_RECENTS_ANIMATION = 4;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_LAUNCHER,
+ TYPE_NOTIFICATION,
+ TYPE_LOCKSCREEN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SourceType {}
+
+ /** The type of the startup source. */
+ public final @SourceType int type;
+
+ /** The timestamp (uptime based) of the source to launch activity. */
+ public final long eventTimeMs;
+
+ SourceInfo(@SourceType int srcType, long uptimeMillis) {
+ type = srcType;
+ eventTimeMs = uptimeMillis;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeLong(eventTimeMs);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<SourceInfo> CREATOR = new Creator<SourceInfo>() {
+ public SourceInfo createFromParcel(Parcel in) {
+ return new SourceInfo(in.readInt(), in.readLong());
+ }
+
+ public SourceInfo[] newArray(int size) {
+ return new SourceInfo[size];
+ }
+ };
+ }
+}
diff --git a/android-34/android/app/ActivityTaskManager.java b/android-34/android/app/ActivityTaskManager.java
new file mode 100644
index 0000000..be8f48d
--- /dev/null
+++ b/android-34/android/app/ActivityTaskManager.java
@@ -0,0 +1,606 @@
+/*
+ * 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.app;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.DisplayMetrics;
+import android.util.Singleton;
+import android.view.RemoteAnimationDefinition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
+
+import java.util.List;
+
+/**
+ * This class gives information about, and interacts with activities and their containers like task,
+ * stacks, and displays.
+ *
+ * @hide
+ */
+@TestApi
+@SystemService(Context.ACTIVITY_TASK_SERVICE)
+public class ActivityTaskManager {
+
+ /** Invalid stack ID. */
+ public static final int INVALID_STACK_ID = -1;
+
+ /**
+ * Invalid task ID.
+ * @hide
+ */
+ public static final int INVALID_TASK_ID = -1;
+
+ /**
+ * Invalid windowing mode.
+ * @hide
+ */
+ public static final int INVALID_WINDOWING_MODE = -1;
+
+ /**
+ * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+ * that the resize doesn't need to preserve the window, and can be skipped if bounds
+ * is unchanged. This mode is used by window manager in most cases.
+ * @hide
+ */
+ public static final int RESIZE_MODE_SYSTEM = 0;
+
+ /**
+ * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+ * that the resize should preserve the window if possible.
+ * @hide
+ */
+ public static final int RESIZE_MODE_PRESERVE_WINDOW = (0x1 << 0);
+
+ /**
+ * Input parameter to {@link IActivityTaskManager#resizeTask} used when the
+ * resize is due to a drag action.
+ * @hide
+ */
+ public static final int RESIZE_MODE_USER = RESIZE_MODE_PRESERVE_WINDOW;
+
+ /**
+ * Input parameter to {@link IActivityTaskManager#resizeTask} used by window
+ * manager during a screen rotation.
+ * @hide
+ */
+ public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = RESIZE_MODE_PRESERVE_WINDOW;
+
+ /**
+ * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+ * that the resize should be performed even if the bounds appears unchanged.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCED = (0x1 << 1);
+
+ /**
+ * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+ * that the resize should preserve the window if possible, and should not be skipped
+ * even if the bounds is unchanged. Usually used to force a resizing when a drag action
+ * is ending.
+ * @hide
+ */
+ public static final int RESIZE_MODE_USER_FORCED =
+ RESIZE_MODE_PRESERVE_WINDOW | RESIZE_MODE_FORCED;
+
+ /**
+ * Extra included on intents that contain an EXTRA_INTENT, with options that the contained
+ * intent may want to be started with. Type is Bundle.
+ * TODO: remove once the ChooserActivity moves to systemui
+ * @hide
+ */
+ public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
+
+ /**
+ * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
+ * parameter of the same name when starting the contained intent.
+ * TODO: remove once the ChooserActivity moves to systemui
+ * @hide
+ */
+ public static final String EXTRA_IGNORE_TARGET_SECURITY =
+ "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
+
+ /** The minimal size of a display's long-edge needed to support split-screen multi-window. */
+ public static final int DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440;
+
+ private static int sMaxRecentTasks = -1;
+
+ private static final Singleton<ActivityTaskManager> sInstance =
+ new Singleton<ActivityTaskManager>() {
+ @Override
+ protected ActivityTaskManager create() {
+ return new ActivityTaskManager();
+ }
+ };
+
+ private ActivityTaskManager() {
+ }
+
+ /** @hide */
+ public static ActivityTaskManager getInstance() {
+ return sInstance.get();
+ }
+
+ /** @hide */
+ public static IActivityTaskManager getService() {
+ return IActivityTaskManagerSingleton.get();
+ }
+
+ @UnsupportedAppUsage(trackingBug = 129726065)
+ private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
+ new Singleton<IActivityTaskManager>() {
+ @Override
+ protected IActivityTaskManager create() {
+ final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
+ return IActivityTaskManager.Stub.asInterface(b);
+ }
+ };
+
+ /**
+ * Removes root tasks in the windowing modes from the system if they are of activity type
+ * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void removeRootTasksInWindowingModes(@NonNull int[] windowingModes) {
+ try {
+ getService().removeRootTasksInWindowingModes(windowingModes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Removes root tasks of the activity types from the system. */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void removeRootTasksWithActivityTypes(@NonNull int[] activityTypes) {
+ try {
+ getService().removeRootTasksWithActivityTypes(activityTypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes all visible recent tasks from the system.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
+ public void removeAllVisibleRecentTasks() {
+ try {
+ getService().removeAllVisibleRecentTasks();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the maximum number of recents entries that we will maintain and show.
+ * @hide
+ */
+ public static int getMaxRecentTasksStatic() {
+ if (sMaxRecentTasks < 0) {
+ return sMaxRecentTasks = ActivityManager.isLowRamDeviceStatic() ? 36 : 48;
+ }
+ return sMaxRecentTasks;
+ }
+
+ /**
+ * Notify the server that splash screen of the given task has been copied"
+ *
+ * @param taskId Id of task to handle the material to reconstruct the splash screen view.
+ * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+ * @hide
+ */
+ public void onSplashScreenViewCopyFinished(int taskId,
+ @Nullable SplashScreenViewParcelable parcelable) {
+ try {
+ getService().onSplashScreenViewCopyFinished(taskId, parcelable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the default limit on the number of recents that an app can make.
+ * @hide
+ */
+ public static int getDefaultAppRecentsLimitStatic() {
+ return getMaxRecentTasksStatic() / 6;
+ }
+
+ /**
+ * Return the maximum limit on the number of recents that an app can make.
+ * @hide
+ */
+ public static int getMaxAppRecentsLimitStatic() {
+ return getMaxRecentTasksStatic() / 2;
+ }
+
+ /**
+ * Returns true if the system supports at least one form of multi-window.
+ * E.g. freeform, split-screen, picture-in-picture.
+ */
+ public static boolean supportsMultiWindow(Context context) {
+ // On watches, multi-window is used to present essential system UI, and thus it must be
+ // supported regardless of device memory characteristics.
+ boolean isWatch = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+ return (!ActivityManager.isLowRamDeviceStatic() || isWatch)
+ && Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_supportsMultiWindow);
+ }
+
+ /**
+ * Returns {@code true} if the display the context is associated with supports split screen
+ * multi-window.
+ *
+ * @throws UnsupportedOperationException if the supplied {@link Context} is not associated with
+ * a display.
+ */
+ public static boolean supportsSplitScreenMultiWindow(Context context) {
+ DisplayMetrics dm = new DisplayMetrics();
+ context.getDisplay().getRealMetrics(dm);
+
+ int widthDp = (int) (dm.widthPixels / dm.density);
+ int heightDp = (int) (dm.heightPixels / dm.density);
+ if (Math.max(widthDp, heightDp) < DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP) {
+ return false;
+ }
+
+ return supportsMultiWindow(context)
+ && Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_supportsSplitScreenMultiWindow);
+ }
+
+ /**
+ * Start to enter lock task mode for given task by system(UI).
+ * @param taskId Id of task to lock.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void startSystemLockTaskMode(int taskId) {
+ try {
+ getService().startSystemLockTaskMode(taskId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stop lock task mode by system(UI).
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void stopSystemLockTaskMode() {
+ try {
+ getService().stopSystemLockTaskMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Move task to root task with given id.
+ * @param taskId Id of the task to move.
+ * @param rootTaskId Id of the rootTask for task moving.
+ * @param toTop Whether the given task should shown to top of stack.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop) {
+ try {
+ getService().moveTaskToRootTask(taskId, rootTaskId, toTop);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resize task to given bounds.
+ * @param taskId Id of task to resize.
+ * @param bounds Bounds to resize task.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void resizeTask(int taskId, Rect bounds) {
+ try {
+ getService().resizeTask(taskId, bounds, RESIZE_MODE_SYSTEM);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears launch params for the given package.
+ * @param packageNames the names of the packages of which the launch params are to be cleared
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void clearLaunchParamsForPackages(List<String> packageNames) {
+ try {
+ getService().clearLaunchParamsForPackages(packageNames);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return whether the UI mode of the given config supports error dialogs (ANR, crash, etc).
+ * @hide
+ */
+ public static boolean currentUiModeSupportsErrorDialogs(@NonNull Configuration config) {
+ int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
+ return (modeType != Configuration.UI_MODE_TYPE_CAR
+ && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
+ && modeType != Configuration.UI_MODE_TYPE_TELEVISION
+ && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
+ }
+
+ /** @return whether the current UI mode supports error dialogs (ANR, crash, etc). */
+ public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) {
+ final Configuration config = context.getResources().getConfiguration();
+ return currentUiModeSupportsErrorDialogs(config);
+ }
+
+ /** @return max allowed number of actions in picture-in-picture mode. */
+ public static int getMaxNumPictureInPictureActions(@NonNull Context context) {
+ return context.getResources().getInteger(
+ com.android.internal.R.integer.config_pictureInPictureMaxNumberOfActions);
+ }
+
+ /**
+ * @return List of running tasks.
+ * @hide
+ */
+ public List<ActivityManager.RunningTaskInfo> getTasks(int maxNum) {
+ return getTasks(maxNum, false /* filterForVisibleRecents */, false /* keepIntentExtra */,
+ INVALID_DISPLAY);
+ }
+
+ /**
+ * @return List of running tasks that can be filtered by visibility in recents.
+ * @hide
+ */
+ public List<ActivityManager.RunningTaskInfo> getTasks(
+ int maxNum, boolean filterOnlyVisibleRecents) {
+ return getTasks(maxNum, filterOnlyVisibleRecents, false /* keepIntentExtra */,
+ INVALID_DISPLAY);
+ }
+
+ /**
+ * @return List of running tasks that can be filtered by visibility in recents and keep intent
+ * extra.
+ * @hide
+ */
+ public List<ActivityManager.RunningTaskInfo> getTasks(
+ int maxNum, boolean filterOnlyVisibleRecents, boolean keepIntentExtra) {
+ return getTasks(maxNum, filterOnlyVisibleRecents, keepIntentExtra, INVALID_DISPLAY);
+ }
+
+ /**
+ * @return List of running tasks that can be filtered by visibility and displayId in recents
+ * and keep intent extra.
+ * @param displayId the target display id, or {@link INVALID_DISPLAY} not to filter by displayId
+ * @hide
+ */
+ public List<ActivityManager.RunningTaskInfo> getTasks(
+ int maxNum, boolean filterOnlyVisibleRecents, boolean keepIntentExtra, int displayId) {
+ try {
+ return getService().getTasks(maxNum, filterOnlyVisibleRecents, keepIntentExtra,
+ displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return List of recent tasks.
+ * @hide
+ */
+ public List<ActivityManager.RecentTaskInfo> getRecentTasks(
+ int maxNum, int flags, int userId) {
+ try {
+ return getService().getRecentTasks(maxNum, flags, userId).getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void registerTaskStackListener(TaskStackListener listener) {
+ try {
+ getService().registerTaskStackListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void unregisterTaskStackListener(TaskStackListener listener) {
+ try {
+ getService().unregisterTaskStackListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public Rect getTaskBounds(int taskId) {
+ try {
+ return getService().getTaskBounds(taskId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers remote animations for a display.
+ * @hide
+ */
+ public void registerRemoteAnimationsForDisplay(
+ int displayId, RemoteAnimationDefinition definition) {
+ try {
+ getService().registerRemoteAnimationsForDisplay(displayId, definition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public boolean isInLockTaskMode() {
+ try {
+ return getService().isInLockTaskMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Removes task by a given taskId */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public boolean removeTask(int taskId) {
+ try {
+ return getService().removeTask(taskId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Detaches the navigation bar from the app it was attached to during a transition.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+ try {
+ getService().detachNavigationBarFromApp(transition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Update the list of packages allowed in lock task mode. */
+ @RequiresPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES)
+ public void updateLockTaskPackages(@NonNull Context context, @NonNull String[] packages) {
+ try {
+ getService().updateLockTaskPackages(context.getUserId(), packages);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Information you can retrieve about a root task in the system.
+ * @hide
+ */
+ public static class RootTaskInfo extends TaskInfo implements Parcelable {
+ // TODO(b/148895075): Move some of the fields to TaskInfo.
+ public Rect bounds = new Rect();
+ public int[] childTaskIds;
+ public String[] childTaskNames;
+ public Rect[] childTaskBounds;
+ public int[] childTaskUserIds;
+ public boolean visible;
+ // Index of the stack in the display's stack list, can be used for comparison of stack order
+ public int position;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(bounds, flags);
+ dest.writeIntArray(childTaskIds);
+ dest.writeStringArray(childTaskNames);
+ dest.writeTypedArray(childTaskBounds, flags);
+ dest.writeIntArray(childTaskUserIds);
+ dest.writeInt(visible ? 1 : 0);
+ dest.writeInt(position);
+ super.writeToParcel(dest, flags);
+ }
+
+ @Override
+ void readFromParcel(Parcel source) {
+ bounds = source.readTypedObject(Rect.CREATOR);
+ childTaskIds = source.createIntArray();
+ childTaskNames = source.createStringArray();
+ childTaskBounds = source.createTypedArray(Rect.CREATOR);
+ childTaskUserIds = source.createIntArray();
+ visible = source.readInt() > 0;
+ position = source.readInt();
+ super.readFromParcel(source);
+ }
+
+ public static final @NonNull Creator<RootTaskInfo> CREATOR = new Creator<>() {
+ @Override
+ public RootTaskInfo createFromParcel(Parcel source) {
+ return new RootTaskInfo(source);
+ }
+
+ @Override
+ public RootTaskInfo[] newArray(int size) {
+ return new RootTaskInfo[size];
+ }
+ };
+
+ public RootTaskInfo() {
+ }
+
+ private RootTaskInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(256);
+ sb.append("RootTask id="); sb.append(taskId);
+ sb.append(" bounds="); sb.append(bounds.toShortString());
+ sb.append(" displayId="); sb.append(displayId);
+ sb.append(" userId="); sb.append(userId);
+ sb.append("\n");
+
+ sb.append(" configuration="); sb.append(configuration);
+ sb.append("\n");
+
+ for (int i = 0; i < childTaskIds.length; ++i) {
+ sb.append(" taskId="); sb.append(childTaskIds[i]);
+ sb.append(": "); sb.append(childTaskNames[i]);
+ if (childTaskBounds != null) {
+ sb.append(" bounds="); sb.append(childTaskBounds[i].toShortString());
+ }
+ sb.append(" userId=").append(childTaskUserIds[i]);
+ sb.append(" visible=").append(visible);
+ if (topActivity != null) {
+ sb.append(" topActivity=").append(topActivity);
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+ }
+}
diff --git a/android-34/android/app/ActivityThread.java b/android-34/android/app/ActivityThread.java
new file mode 100644
index 0000000..4c90d7b
--- /dev/null
+++ b/android-34/android/app/ActivityThread.java
@@ -0,0 +1,8273 @@
+/*
+ * 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.app;
+
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
+import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
+import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
+import static android.window.ConfigurationHelper.isDifferentDisplay;
+import static android.window.ConfigurationHelper.shouldUpdateResources;
+import static android.window.ConfigurationHelper.shouldUpdateWindowMetricsBounds;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
+import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
+import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
+import android.app.RemoteServiceException.CrashedByAdbException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.app.compat.CompatChanges;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import android.app.servertransaction.ActivityRelaunchItem;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.PendingTransactionActions;
+import android.app.servertransaction.PendingTransactionActions.StopInfo;
+import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.TransactionExecutor;
+import android.app.servertransaction.TransactionExecutorHelper;
+import android.bluetooth.BluetoothFrameworkInitializer;
+import android.companion.virtual.VirtualDeviceManager;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
+import android.content.AutofillOptions;
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ProviderInfoList;
+import android.content.pm.ServiceInfo;
+import android.content.res.AssetManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.loader.ResourcesLoader;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteDebug.DbStats;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
+import android.graphics.Typeface;
+import android.hardware.display.DisplayManagerGlobal;
+import android.media.MediaFrameworkInitializer;
+import android.media.MediaFrameworkPlatformInitializer;
+import android.media.MediaServiceManager;
+import android.net.ConnectivityManager;
+import android.net.Proxy;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.nfc.NfcFrameworkInitializer;
+import android.nfc.NfcServiceManager;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.BluetoothServiceManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.GraphicsEnvironment;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SharedMemory;
+import android.os.StatsFrameworkInitializer;
+import android.os.StatsServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.TelephonyServiceManager;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.IPermissionManager;
+import android.provider.BlockedNumberContract;
+import android.provider.CalendarContract;
+import android.provider.CallLog;
+import android.provider.ContactsContract;
+import android.provider.DeviceConfigInitializer;
+import android.provider.DeviceConfigServiceManager;
+import android.provider.Downloads;
+import android.provider.FontsContract;
+import android.provider.Settings;
+import android.renderscript.RenderScriptCacheDir;
+import android.security.NetworkSecurityPolicy;
+import android.security.net.config.NetworkSecurityConfigProvider;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.MergedConfiguration;
+import android.util.Pair;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SuperNotCalledException;
+import android.util.UtilConfig;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.ViewManager;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.IContentCaptureOptionsCallback;
+import android.view.translation.TranslationSpec;
+import android.view.translation.UiTranslationSpec;
+import android.webkit.WebView;
+import android.window.SizeConfigurationBuckets;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
+import android.window.WindowProviderService;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SafeZipPathValidatorCallback;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.DecorView;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.org.conscrypt.TrustedCertificateStore;
+import com.android.server.am.MemInfoDumpProto;
+
+import dalvik.system.AppSpecializationHooks;
+import dalvik.system.CloseGuard;
+import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
+
+import libcore.io.ForwardingOs;
+import libcore.io.IoUtils;
+import libcore.io.Os;
+import libcore.net.event.NetworkEventDispatcher;
+
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This manages the execution of the main thread in an
+ * application process, scheduling and executing activities,
+ * broadcasts, and other operations on it as the activity
+ * manager requests.
+ *
+ * {@hide}
+ */
+public final class ActivityThread extends ClientTransactionHandler
+ implements ActivityThreadInternal {
+ /** @hide */
+ public static final String TAG = "ActivityThread";
+ private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
+ static final boolean localLOGV = false;
+ static final boolean DEBUG_MESSAGES = false;
+ /** @hide */
+ public static final boolean DEBUG_BROADCAST = false;
+ private static final boolean DEBUG_RESULTS = false;
+ private static final boolean DEBUG_BACKUP = false;
+ public static final boolean DEBUG_CONFIGURATION = false;
+ private static final boolean DEBUG_SERVICE = false;
+ public static final boolean DEBUG_MEMORY_TRIM = false;
+ private static final boolean DEBUG_PROVIDER = false;
+ public static final boolean DEBUG_ORDER = false;
+ private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
+ /**
+ * The delay to release the provider when it has no more references. It reduces the number of
+ * transactions for acquiring and releasing provider if the client accesses the provider
+ * frequently in a short time.
+ */
+ private static final long CONTENT_PROVIDER_RETAIN_TIME = 1000;
+ private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
+
+ /** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
+ public static final int SERVICE_DONE_EXECUTING_ANON = 0;
+ /** Type for IActivityManager.serviceDoneExecuting: done with an onStart call */
+ public static final int SERVICE_DONE_EXECUTING_START = 1;
+ /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
+ public static final int SERVICE_DONE_EXECUTING_STOP = 2;
+
+ /** Use foreground GC policy (less pause time) and higher JIT weight. */
+ private static final int VM_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
+ /** Use background GC policy and default JIT threshold. */
+ private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
+
+ /** The delay time for retrying to request DirectActions. */
+ private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
+ /** The max count for retrying to request DirectActions. */
+ private static final int REQUEST_DIRECT_ACTIONS_RETRY_MAX_COUNT = 7;
+
+ /**
+ * Denotes an invalid sequence number corresponding to a process state change.
+ */
+ public static final long INVALID_PROC_STATE_SEQ = -1;
+
+ /**
+ * Identifier for the sequence no. associated with this process start. It will be provided
+ * as one of the arguments when the process starts.
+ */
+ public static final String PROC_START_SEQ_IDENT = "seq=";
+
+ private final Object mNetworkPolicyLock = new Object();
+
+ private static final String DEFAULT_FULL_BACKUP_AGENT = "android.app.backup.FullBackupAgent";
+
+ /**
+ * Denotes the sequence number of the process state change for which the main thread needs
+ * to block until the network rules are updated for it.
+ *
+ * Value of {@link #INVALID_PROC_STATE_SEQ} indicates there is no need for blocking.
+ */
+ @GuardedBy("mNetworkPolicyLock")
+ private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+
+ @UnsupportedAppUsage
+ private ContextImpl mSystemContext;
+ @GuardedBy("this")
+ private SparseArray<ContextImpl> mDisplaySystemUiContexts;
+
+ @UnsupportedAppUsage
+ static volatile IPackageManager sPackageManager;
+ private static volatile IPermissionManager sPermissionManager;
+
+ @UnsupportedAppUsage
+ final ApplicationThread mAppThread = new ApplicationThread();
+ @UnsupportedAppUsage
+ final Looper mLooper = Looper.myLooper();
+ @UnsupportedAppUsage
+ final H mH = new H();
+ final Executor mExecutor = new HandlerExecutor(mH);
+ /**
+ * Maps from activity token to local record of running activities in this process.
+ *
+ * This variable is readable if the code is running in activity thread or holding {@link
+ * #mResourcesManager}. It's only writable if the code is running in activity thread and holding
+ * {@link #mResourcesManager}.
+ */
+ @UnsupportedAppUsage
+ final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
+ /** Maps from activity token to the pending override configuration. */
+ @GuardedBy("mPendingOverrideConfigs")
+ private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>();
+ /** The activities to be truly destroyed (not include relaunch). */
+ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
+ Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
+ // List of new activities that should be reported when next we idle.
+ final ArrayList<ActivityClientRecord> mNewActivities = new ArrayList<>();
+ // Number of activities that are currently visible on-screen.
+ @UnsupportedAppUsage
+ int mNumVisibleActivities = 0;
+ private final AtomicInteger mNumLaunchingActivities = new AtomicInteger();
+ @GuardedBy("mAppThread")
+ private int mLastProcessState = PROCESS_STATE_UNKNOWN;
+ ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
+ private int mLastSessionId;
+ // Holds the value of the last reported device ID value from the server for the top activity.
+ int mLastReportedDeviceId;
+ final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
+ @UnsupportedAppUsage
+ final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
+ @UnsupportedAppUsage
+ AppBindData mBoundApplication;
+ Profiler mProfiler;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553,
+ publicAlternatives = "Use {@code Context#getResources()#getConfiguration()#densityDpi} "
+ + "instead.")
+ int mCurDefaultDisplayDpi;
+ @UnsupportedAppUsage
+ boolean mDensityCompatMode;
+ private CompatibilityInfo mCompatibilityInfo;
+ @UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R,
+ publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.")
+ Configuration mConfiguration;
+ @GuardedBy("this")
+ private boolean mUpdateHttpProxyOnBind = false;
+ @UnsupportedAppUsage
+ Application mInitialApplication;
+ @UnsupportedAppUsage
+ final ArrayList<Application> mAllApplications = new ArrayList<>();
+ /**
+ * Bookkeeping of instantiated backup agents indexed first by user id, then by package name.
+ * Indexing by user id supports parallel backups across users on system packages as they run in
+ * the same process with the same package name. Indexing by package name supports multiple
+ * distinct applications running in the same process.
+ */
+ private final SparseArray<ArrayMap<String, BackupAgent>> mBackupAgentsByUser =
+ new SparseArray<>();
+ /** Reference to singleton {@link ActivityThread} */
+ @UnsupportedAppUsage
+ private static volatile ActivityThread sCurrentActivityThread;
+ @UnsupportedAppUsage
+ Instrumentation mInstrumentation;
+ String mInstrumentationPackageName = null;
+ @UnsupportedAppUsage
+ String mInstrumentationAppDir = null;
+ String[] mInstrumentationSplitAppDirs = null;
+ String mInstrumentationLibDir = null;
+ @UnsupportedAppUsage
+ String mInstrumentedAppDir = null;
+ String[] mInstrumentedSplitAppDirs = null;
+ String mInstrumentedLibDir = null;
+ boolean mInstrumentingWithoutRestart;
+ boolean mSystemThread = false;
+ boolean mSomeActivitiesChanged = false;
+
+ // These can be accessed by multiple threads; mResourcesManager is the lock.
+ // XXX For now we keep around information about all packages we have
+ // seen, not removing entries from this map.
+ // NOTE: The activity and window managers need to call in to
+ // ActivityThread to do things like update resource configurations,
+ // which means this lock gets held while the activity and window managers
+ // holds their own lock. Thus you MUST NEVER call back into the activity manager
+ // or window manager or anything that depends on them while holding this lock.
+ // These LoadedApk are only valid for the userId that we're running as.
+ @GuardedBy("mResourcesManager")
+ @UnsupportedAppUsage
+ final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
+ @GuardedBy("mResourcesManager")
+ @UnsupportedAppUsage
+ final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
+ @GuardedBy("mResourcesManager")
+ final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>();
+ @GuardedBy("mResourcesManager")
+ @UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R,
+ publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.")
+ Configuration mPendingConfiguration = null;
+ // An executor that performs multi-step transactions.
+ private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final ResourcesManager mResourcesManager;
+
+ // Registry of remote cancellation transports pending a reply with reply handles.
+ @GuardedBy("this")
+ private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
+
+ private static final class ProviderKey {
+ final String authority;
+ final int userId;
+
+ @GuardedBy("mLock")
+ ContentProviderHolder mHolder; // Temp holder to be used between notifier and waiter
+ final Object mLock; // The lock to be used to get notified when the provider is ready
+
+ public ProviderKey(String authority, int userId) {
+ this.authority = authority;
+ this.userId = userId;
+ this.mLock = new Object();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof ProviderKey) {
+ final ProviderKey other = (ProviderKey) o;
+ return Objects.equals(authority, other.authority) && userId == other.userId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((authority != null) ? authority.hashCode() : 0) ^ userId;
+ }
+ }
+
+ // The lock of mProviderMap protects the following variables.
+ @UnsupportedAppUsage
+ final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
+ = new ArrayMap<ProviderKey, ProviderClientRecord>();
+ @UnsupportedAppUsage
+ final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap
+ = new ArrayMap<IBinder, ProviderRefCount>();
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders
+ = new ArrayMap<IBinder, ProviderClientRecord>();
+ @UnsupportedAppUsage
+ final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName
+ = new ArrayMap<ComponentName, ProviderClientRecord>();
+
+ // Mitigation for b/74523247: Used to serialize calls to AM.getContentProvider().
+ // Note we never removes items from this map but that's okay because there are only so many
+ // users and so many authorities.
+ @GuardedBy("mGetProviderKeys")
+ final ArrayMap<ProviderKey, ProviderKey> mGetProviderKeys = new ArrayMap<>();
+
+ final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
+ = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
+
+ private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;
+
+ final GcIdler mGcIdler = new GcIdler();
+ final PurgeIdler mPurgeIdler = new PurgeIdler();
+
+ boolean mPurgeIdlerScheduled = false;
+ boolean mGcIdlerScheduled = false;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ static volatile Handler sMainThreadHandler; // set once in main()
+ private long mStartSeq; // Only accesssed from the main thread
+
+ Bundle mCoreSettings = null;
+
+ /**
+ * The lock word for the {@link #mCoreSettings}.
+ */
+ private final Object mCoreSettingsLock = new Object();
+
+ private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null;
+
+ /** A client side controller to handle process level configuration changes. */
+ private ConfigurationController mConfigurationController;
+
+ /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */
+ public static final class ActivityClientRecord {
+ @UnsupportedAppUsage
+ public IBinder token;
+ public IBinder assistToken;
+ // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
+ // used without security checks
+ public IBinder shareableActivityToken;
+ // The token of the TaskFragment that embedded this activity.
+ @Nullable public IBinder mTaskFragmentToken;
+ int ident;
+ @UnsupportedAppUsage
+ Intent intent;
+ String referrer;
+ IVoiceInteractor voiceInteractor;
+ Bundle state;
+ PersistableBundle persistentState;
+ @UnsupportedAppUsage
+ Activity activity;
+ Window window;
+ Activity parent;
+ String embeddedID;
+ Activity.NonConfigurationInstances lastNonConfigurationInstances;
+ // TODO(lifecycler): Use mLifecycleState instead.
+ @UnsupportedAppUsage
+ boolean paused;
+ @UnsupportedAppUsage
+ boolean stopped;
+ boolean hideForNow;
+ Configuration createdConfig;
+ Configuration overrideConfig;
+ // Used for consolidating configs before sending on to Activity.
+ private Configuration tmpConfig = new Configuration();
+ // Callback used for updating activity override config and camera compat control state.
+ ViewRootImpl.ActivityConfigCallback activityConfigCallback;
+
+ // Indicates whether this activity is currently the topmost resumed one in the system.
+ // This holds the last reported value from server.
+ boolean isTopResumedActivity;
+ // This holds the value last sent to the activity. This is needed, because an update from
+ // server may come at random time, but we always need to report changes between ON_RESUME
+ // and ON_PAUSE to the app.
+ boolean lastReportedTopResumedState;
+
+ ProfilerInfo profilerInfo;
+
+ @UnsupportedAppUsage
+ ActivityInfo activityInfo;
+ @UnsupportedAppUsage
+ CompatibilityInfo compatInfo;
+ @UnsupportedAppUsage
+ public LoadedApk packageInfo;
+
+ List<ResultInfo> pendingResults;
+ List<ReferrerIntent> pendingIntents;
+
+ boolean startsNotResumed;
+ public final boolean isForward;
+ int pendingConfigChanges;
+ // Whether we are in the process of performing on user leaving.
+ boolean mIsUserLeaving;
+
+ Window mPendingRemoveWindow;
+ WindowManager mPendingRemoveWindowManager;
+ @UnsupportedAppUsage
+ boolean mPreserveWindow;
+
+ /** The options for scene transition. */
+ ActivityOptions mActivityOptions;
+
+ /** Whether this activiy was launched from a bubble. */
+ boolean mLaunchedFromBubble;
+
+ /**
+ * This can be different from the current configuration because a new configuration may not
+ * always update to activity, e.g. windowing mode change without size change.
+ */
+ int mLastReportedWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+ @LifecycleState
+ private int mLifecycleState = PRE_ON_CREATE;
+
+ private SizeConfigurationBuckets mSizeConfigurations;
+
+ @VisibleForTesting
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public ActivityClientRecord() {
+ this.isForward = false;
+ init();
+ }
+
+ public ActivityClientRecord(IBinder token, Intent intent, int ident,
+ ActivityInfo info, Configuration overrideConfig,
+ String referrer, IVoiceInteractor voiceInteractor, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
+ boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
+ IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
+ IBinder taskFragmentToken) {
+ this.token = token;
+ this.assistToken = assistToken;
+ this.shareableActivityToken = shareableActivityToken;
+ this.ident = ident;
+ this.intent = intent;
+ this.referrer = referrer;
+ this.voiceInteractor = voiceInteractor;
+ this.activityInfo = info;
+ this.state = state;
+ this.persistentState = persistentState;
+ this.pendingResults = pendingResults;
+ this.pendingIntents = pendingNewIntents;
+ this.isForward = isForward;
+ this.profilerInfo = profilerInfo;
+ this.overrideConfig = overrideConfig;
+ this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
+ mActivityOptions = activityOptions;
+ mLaunchedFromBubble = launchedFromBubble;
+ mTaskFragmentToken = taskFragmentToken;
+ init();
+ }
+
+ /** Common initializer for all constructors. */
+ private void init() {
+ parent = null;
+ embeddedID = null;
+ paused = false;
+ stopped = false;
+ hideForNow = false;
+ activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {
+ @Override
+ public void onConfigurationChanged(Configuration overrideConfig,
+ int newDisplayId) {
+ if (activity == null) {
+ throw new IllegalStateException(
+ "Received config update for non-existing activity");
+ }
+ activity.mMainThread.handleActivityConfigurationChanged(
+ ActivityClientRecord.this, overrideConfig, newDisplayId,
+ false /* alwaysReportChange */);
+ }
+
+ @Override
+ public void requestCompatCameraControl(boolean showControl,
+ boolean transformationApplied, ICompatCameraControlCallback callback) {
+ if (activity == null) {
+ throw new IllegalStateException(
+ "Received camera compat control update for non-existing activity");
+ }
+ ActivityClient.getInstance().requestCompatCameraControl(
+ activity.getResources(), token, showControl, transformationApplied,
+ callback);
+ }
+
+ };
+ }
+
+ /** Get the current lifecycle state. */
+ public int getLifecycleState() {
+ return mLifecycleState;
+ }
+
+ /** Update the current lifecycle state for internal bookkeeping. */
+ public void setState(@LifecycleState int newLifecycleState) {
+ mLifecycleState = newLifecycleState;
+ switch (mLifecycleState) {
+ case ON_CREATE:
+ paused = true;
+ stopped = true;
+ break;
+ case ON_START:
+ paused = true;
+ stopped = false;
+ break;
+ case ON_RESUME:
+ paused = false;
+ stopped = false;
+ break;
+ case ON_PAUSE:
+ paused = true;
+ stopped = false;
+ break;
+ case ON_STOP:
+ paused = true;
+ stopped = true;
+ break;
+ }
+ }
+
+ private boolean isPreHoneycomb() {
+ return activity != null && activity.getApplicationInfo().targetSdkVersion
+ < android.os.Build.VERSION_CODES.HONEYCOMB;
+ }
+
+ private boolean isPreP() {
+ return activity != null && activity.getApplicationInfo().targetSdkVersion
+ < android.os.Build.VERSION_CODES.P;
+ }
+
+ public boolean isPersistable() {
+ return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS;
+ }
+
+ public boolean isVisibleFromServer() {
+ return activity != null && activity.mVisibleFromServer;
+ }
+
+ public String toString() {
+ ComponentName componentName = intent != null ? intent.getComponent() : null;
+ return "ActivityRecord{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " token=" + token + " " + (componentName == null
+ ? "no component name" : componentName.toShortString())
+ + "}";
+ }
+
+ public String getStateString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ActivityClientRecord{");
+ sb.append("paused=").append(paused);
+ sb.append(", stopped=").append(stopped);
+ sb.append(", hideForNow=").append(hideForNow);
+ sb.append(", startsNotResumed=").append(startsNotResumed);
+ sb.append(", isForward=").append(isForward);
+ sb.append(", pendingConfigChanges=").append(pendingConfigChanges);
+ sb.append(", preserveWindow=").append(mPreserveWindow);
+ if (activity != null) {
+ sb.append(", Activity{");
+ sb.append("resumed=").append(activity.mResumed);
+ sb.append(", stopped=").append(activity.mStopped);
+ sb.append(", finished=").append(activity.isFinishing());
+ sb.append(", destroyed=").append(activity.isDestroyed());
+ sb.append(", startedActivity=").append(activity.mStartedActivity);
+ sb.append(", changingConfigurations=").append(activity.mChangingConfigurations);
+ sb.append("}");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+ }
+
+ final class ProviderClientRecord {
+ final String[] mNames;
+ @UnsupportedAppUsage
+ final IContentProvider mProvider;
+ @UnsupportedAppUsage
+ final ContentProvider mLocalProvider;
+ @UnsupportedAppUsage
+ final ContentProviderHolder mHolder;
+
+ ProviderClientRecord(String[] names, IContentProvider provider,
+ ContentProvider localProvider, ContentProviderHolder holder) {
+ mNames = names;
+ mProvider = provider;
+ mLocalProvider = localProvider;
+ mHolder = holder;
+ }
+ }
+
+ static final class ReceiverData extends BroadcastReceiver.PendingResult {
+ public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
+ boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
+ int sendingUser, int sendingUid, String sendingPackage) {
+ super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
+ assumeDelivered, token, sendingUser, intent.getFlags(), sendingUid,
+ sendingPackage);
+ this.intent = intent;
+ }
+
+ @UnsupportedAppUsage
+ Intent intent;
+ @UnsupportedAppUsage
+ ActivityInfo info;
+ @UnsupportedAppUsage
+ CompatibilityInfo compatInfo;
+ public String toString() {
+ return "ReceiverData{intent=" + intent + " packageName=" +
+ info.packageName + " resultCode=" + getResultCode()
+ + " resultData=" + getResultData() + " resultExtras="
+ + getResultExtras(false) + " sentFromUid="
+ + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + "}";
+ }
+ }
+
+ static final class CreateBackupAgentData {
+ ApplicationInfo appInfo;
+ int backupMode;
+ int userId;
+ @BackupDestination int backupDestination;
+ public String toString() {
+ return "CreateBackupAgentData{appInfo=" + appInfo
+ + " backupAgent=" + appInfo.backupAgentName
+ + " mode=" + backupMode + " userId=" + userId + "}";
+ }
+ }
+
+ static final class CreateServiceData {
+ @UnsupportedAppUsage
+ CreateServiceData() {
+ }
+ @UnsupportedAppUsage
+ IBinder token;
+ @UnsupportedAppUsage
+ ServiceInfo info;
+ @UnsupportedAppUsage
+ CompatibilityInfo compatInfo;
+ @UnsupportedAppUsage
+ Intent intent;
+ public String toString() {
+ return "CreateServiceData{token=" + token + " className="
+ + info.name + " packageName=" + info.packageName
+ + " intent=" + intent + "}";
+ }
+ }
+
+ static final class BindServiceData {
+ @UnsupportedAppUsage
+ IBinder token;
+ @UnsupportedAppUsage
+ Intent intent;
+ boolean rebind;
+ long bindSeq;
+ public String toString() {
+ return "BindServiceData{token=" + token + " intent=" + intent
+ + " bindSeq=" + bindSeq + "}";
+ }
+ }
+
+ static final class ServiceArgsData {
+ @UnsupportedAppUsage
+ IBinder token;
+ boolean taskRemoved;
+ int startId;
+ int flags;
+ @UnsupportedAppUsage
+ Intent args;
+ public String toString() {
+ return "ServiceArgsData{token=" + token + " startId=" + startId
+ + " args=" + args + "}";
+ }
+ }
+
+ static final class AppBindData {
+ @UnsupportedAppUsage
+ AppBindData() {
+ }
+ @UnsupportedAppUsage
+ LoadedApk info;
+ @UnsupportedAppUsage
+ String processName;
+ @UnsupportedAppUsage
+ ApplicationInfo appInfo;
+ String sdkSandboxClientAppVolumeUuid;
+ String sdkSandboxClientAppPackage;
+ @UnsupportedAppUsage
+ List<ProviderInfo> providers;
+ ComponentName instrumentationName;
+ @UnsupportedAppUsage
+ Bundle instrumentationArgs;
+ IInstrumentationWatcher instrumentationWatcher;
+ IUiAutomationConnection instrumentationUiAutomationConnection;
+ int debugMode;
+ boolean enableBinderTracking;
+ boolean trackAllocation;
+ @UnsupportedAppUsage
+ boolean restrictedBackupMode;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ boolean persistent;
+ Configuration config;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ CompatibilityInfo compatInfo;
+ String buildSerial;
+
+ /** Initial values for {@link Profiler}. */
+ ProfilerInfo initProfilerInfo;
+
+ AutofillOptions autofillOptions;
+
+ /**
+ * Content capture options for the application - when null, it means ContentCapture is not
+ * enabled for the package.
+ */
+ @Nullable
+ ContentCaptureOptions contentCaptureOptions;
+
+ long[] disabledCompatChanges;
+
+ SharedMemory mSerializedSystemFontMap;
+
+ long startRequestedElapsedTime;
+ long startRequestedUptime;
+
+ @Override
+ public String toString() {
+ return "AppBindData{appInfo=" + appInfo + "}";
+ }
+ }
+
+ static final class Profiler {
+ String profileFile;
+ ParcelFileDescriptor profileFd;
+ int samplingInterval;
+ boolean autoStopProfiler;
+ boolean streamingOutput;
+ int mClockType;
+ boolean profiling;
+ boolean handlingProfiling;
+ public void setProfiler(ProfilerInfo profilerInfo) {
+ ParcelFileDescriptor fd = profilerInfo.profileFd;
+ if (profiling) {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ return;
+ }
+ if (profileFd != null) {
+ try {
+ profileFd.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ profileFile = profilerInfo.profileFile;
+ profileFd = fd;
+ samplingInterval = profilerInfo.samplingInterval;
+ autoStopProfiler = profilerInfo.autoStopProfiler;
+ streamingOutput = profilerInfo.streamingOutput;
+ mClockType = profilerInfo.clockType;
+ }
+ public void startProfiling() {
+ if (profileFd == null || profiling) {
+ return;
+ }
+ try {
+ int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
+ VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
+ bufferSize * 1024 * 1024, mClockType, samplingInterval != 0,
+ samplingInterval, streamingOutput);
+ profiling = true;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Profiling failed on path " + profileFile, e);
+ try {
+ profileFd.close();
+ profileFd = null;
+ } catch (IOException e2) {
+ Slog.w(TAG, "Failure closing profile fd", e2);
+ }
+ }
+ }
+ public void stopProfiling() {
+ if (profiling) {
+ profiling = false;
+ Debug.stopMethodTracing();
+ if (profileFd != null) {
+ try {
+ profileFd.close();
+ } catch (IOException e) {
+ }
+ }
+ profileFd = null;
+ profileFile = null;
+ }
+ }
+ }
+
+ static final class DumpComponentInfo {
+ ParcelFileDescriptor fd;
+ IBinder token;
+ String prefix;
+ String[] args;
+ }
+
+ static final class ContextCleanupInfo {
+ ContextImpl context;
+ String what;
+ String who;
+ }
+
+ static final class DumpHeapData {
+ // Whether to dump the native or managed heap.
+ public boolean managed;
+ public boolean mallocInfo;
+ public boolean runGc;
+ String path;
+ ParcelFileDescriptor fd;
+ RemoteCallback finishCallback;
+ }
+
+ static final class DumpResourcesData {
+ public ParcelFileDescriptor fd;
+ public RemoteCallback finishCallback;
+ }
+
+ static final class UpdateCompatibilityData {
+ String pkg;
+ CompatibilityInfo info;
+ }
+
+ static final class RequestAssistContextExtras {
+ IBinder activityToken;
+ IBinder requestToken;
+ int requestType;
+ int sessionId;
+ int flags;
+ }
+
+ // A list of receivers and an index into the receiver to be processed next.
+ static final class ReceiverList {
+ List<ReceiverInfo> receivers;
+ int index;
+ }
+
+ private class ApplicationThread extends IApplicationThread.Stub {
+ private static final String DB_CONNECTION_INFO_HEADER = " %8s %8s %14s %5s %5s %5s %s";
+ private static final String DB_CONNECTION_INFO_FORMAT = " %8s %8s %14s %5d %5d %5d %s";
+ private static final String DB_POOL_INFO_HEADER = " %13s %13s %13s %s";
+ private static final String DB_POOL_INFO_FORMAT = " %13d %13d %13d %s";
+
+ public final void scheduleReceiver(Intent intent, ActivityInfo info,
+ CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
+ boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
+ int sendingUid, String sendingPackage) {
+ updateProcessState(processState, false);
+ ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
+ ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
+ sendingUid, sendingPackage);
+ r.info = info;
+ sendMessage(H.RECEIVER, r);
+ }
+
+ public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
+ for (int i = 0; i < info.size(); i++) {
+ ReceiverInfo r = info.get(i);
+ if (r.registered) {
+ scheduleRegisteredReceiver(r.receiver, r.intent,
+ r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+ r.assumeDelivered, r.sendingUser, r.processState,
+ r.sendingUid, r.sendingPackage);
+ } else {
+ scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+ r.resultCode, r.data, r.extras, r.sync,
+ r.assumeDelivered, r.sendingUser, r.processState,
+ r.sendingUid, r.sendingPackage);
+ }
+ }
+ }
+
+ public final void scheduleCreateBackupAgent(ApplicationInfo app,
+ int backupMode, int userId, @BackupDestination int backupDestination) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+ d.backupMode = backupMode;
+ d.userId = userId;
+ d.backupDestination = backupDestination;
+
+ sendMessage(H.CREATE_BACKUP_AGENT, d);
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app, int userId) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+ d.userId = userId;
+
+ sendMessage(H.DESTROY_BACKUP_AGENT, d);
+ }
+
+ public final void scheduleCreateService(IBinder token,
+ ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
+ updateProcessState(processState, false);
+ CreateServiceData s = new CreateServiceData();
+ s.token = token;
+ s.info = info;
+
+ sendMessage(H.CREATE_SERVICE, s);
+ }
+
+ public final void scheduleBindService(IBinder token, Intent intent,
+ boolean rebind, int processState, long bindSeq) {
+ updateProcessState(processState, false);
+ BindServiceData s = new BindServiceData();
+ s.token = token;
+ s.intent = intent;
+ s.rebind = rebind;
+ s.bindSeq = bindSeq;
+
+ if (DEBUG_SERVICE)
+ Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
+ + Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
+ sendMessage(H.BIND_SERVICE, s);
+ }
+
+ public final void scheduleUnbindService(IBinder token, Intent intent) {
+ BindServiceData s = new BindServiceData();
+ s.token = token;
+ s.intent = intent;
+ s.bindSeq = -1;
+
+ sendMessage(H.UNBIND_SERVICE, s);
+ }
+
+ public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
+ List<ServiceStartArgs> list = args.getList();
+
+ for (int i = 0; i < list.size(); i++) {
+ ServiceStartArgs ssa = list.get(i);
+ ServiceArgsData s = new ServiceArgsData();
+ s.token = token;
+ s.taskRemoved = ssa.taskRemoved;
+ s.startId = ssa.startId;
+ s.flags = ssa.flags;
+ s.args = ssa.args;
+
+ sendMessage(H.SERVICE_ARGS, s);
+ }
+ }
+
+ public final void scheduleStopService(IBinder token) {
+ sendMessage(H.STOP_SERVICE, token);
+ }
+
+ @Override
+ public final void scheduleTimeoutService(IBinder token, int startId) {
+ sendMessage(H.TIMEOUT_SERVICE, token, startId);
+ }
+
+ @Override
+ public final void schedulePing(RemoteCallback pong) {
+ sendMessage(H.PING, pong);
+ }
+
+ @Override
+ public final void bindApplication(String processName, ApplicationInfo appInfo,
+ String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
+ ProviderInfoList providerList, ComponentName instrumentationName,
+ ProfilerInfo profilerInfo, Bundle instrumentationArgs,
+ IInstrumentationWatcher instrumentationWatcher,
+ IUiAutomationConnection instrumentationUiConnection, int debugMode,
+ boolean enableBinderTracking, boolean trackAllocation,
+ boolean isRestrictedBackupMode, boolean persistent, Configuration config,
+ CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
+ String buildSerial, AutofillOptions autofillOptions,
+ ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
+ SharedMemory serializedSystemFontMap,
+ long startRequestedElapsedTime, long startRequestedUptime) {
+ if (services != null) {
+ if (false) {
+ // Test code to make sure the app could see the passed-in services.
+ for (Object oname : services.keySet()) {
+ if (services.get(oname) == null) {
+ continue; // AM just passed in a null service.
+ }
+ String name = (String) oname;
+
+ // See b/79378449 about the following exemption.
+ switch (name) {
+ case "package":
+ case Context.WINDOW_SERVICE:
+ continue;
+ }
+
+ if (ServiceManager.getService(name) == null) {
+ Log.wtf(TAG, "Service " + name + " should be accessible by this app");
+ }
+ }
+ }
+
+ // Setup the service cache in the ServiceManager
+ ServiceManager.initServiceCache(services);
+ }
+
+ setCoreSettings(coreSettings);
+
+ AppBindData data = new AppBindData();
+ data.processName = processName;
+ data.appInfo = appInfo;
+ data.sdkSandboxClientAppVolumeUuid = sdkSandboxClientAppVolumeUuid;
+ data.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
+ data.providers = providerList.getList();
+ data.instrumentationName = instrumentationName;
+ data.instrumentationArgs = instrumentationArgs;
+ data.instrumentationWatcher = instrumentationWatcher;
+ data.instrumentationUiAutomationConnection = instrumentationUiConnection;
+ data.debugMode = debugMode;
+ data.enableBinderTracking = enableBinderTracking;
+ data.trackAllocation = trackAllocation;
+ data.restrictedBackupMode = isRestrictedBackupMode;
+ data.persistent = persistent;
+ data.config = config;
+ data.compatInfo = compatInfo;
+ data.initProfilerInfo = profilerInfo;
+ data.buildSerial = buildSerial;
+ data.autofillOptions = autofillOptions;
+ data.contentCaptureOptions = contentCaptureOptions;
+ data.disabledCompatChanges = disabledCompatChanges;
+ data.mSerializedSystemFontMap = serializedSystemFontMap;
+ data.startRequestedElapsedTime = startRequestedElapsedTime;
+ data.startRequestedUptime = startRequestedUptime;
+ updateCompatOverrideScale(compatInfo);
+ CompatibilityInfo.applyOverrideScaleIfNeeded(config);
+ sendMessage(H.BIND_APPLICATION, data);
+ }
+
+ private void updateCompatOverrideScale(CompatibilityInfo info) {
+ CompatibilityInfo.setOverrideInvertedScale(
+ info.hasOverrideScaling() ? info.applicationInvertedScale : 1f);
+ }
+
+ public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = entryPoint;
+ args.arg2 = entryPointArgs;
+ sendMessage(H.RUN_ISOLATED_ENTRY_POINT, args);
+ }
+
+ public final void scheduleExit() {
+ sendMessage(H.EXIT_APPLICATION, null);
+ }
+
+ public final void scheduleSuicide() {
+ sendMessage(H.SUICIDE, null);
+ }
+
+ public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
+ mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai);
+ mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
+ sendMessage(H.APPLICATION_INFO_CHANGED, ai);
+ }
+
+ public void updateTimeZone() {
+ TimeZone.setDefault(null);
+ }
+
+ public void clearDnsCache() {
+ // a non-standard API to get this to libcore
+ InetAddress.clearDnsCache();
+ // Allow libcore to perform the necessary actions as it sees fit upon a network
+ // configuration change.
+ NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();
+ }
+
+ public void updateHttpProxy() {
+ final Application app;
+ synchronized (ActivityThread.this) {
+ app = getApplication();
+ if (null == app) {
+ // The app is not bound yet. Make a note to update the HTTP proxy when the
+ // app is bound.
+ mUpdateHttpProxyOnBind = true;
+ return;
+ }
+ }
+ // App is present, update the proxy inline.
+ ActivityThread.updateHttpProxy(app);
+ }
+
+ public void processInBackground() {
+ mH.removeMessages(H.GC_WHEN_IDLE);
+ mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
+ }
+
+ public void dumpService(ParcelFileDescriptor pfd, IBinder servicetoken, String[] args) {
+ DumpComponentInfo data = new DumpComponentInfo();
+ try {
+ data.fd = pfd.dup();
+ data.token = servicetoken;
+ data.args = args;
+ sendMessage(H.DUMP_SERVICE, data, 0, 0, true /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpService failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ // This function exists to make sure all receiver dispatching is
+ // correctly ordered, since these are one-way calls and the binder driver
+ // applies transaction ordering per object for such calls.
+ public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
+ int resultCode, String dataStr, Bundle extras, boolean ordered,
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
+ int sendingUid, String sendingPackage)
+ throws RemoteException {
+ updateProcessState(processState, false);
+
+ // We can't modify IIntentReceiver due to UnsupportedAppUsage, so
+ // try our best to shortcut to known subclasses, and alert if
+ // registered using a custom IIntentReceiver that isn't able to
+ // report an expected delivery event
+ if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
+ ((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
+ resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser,
+ sendingUid, sendingPackage);
+ } else {
+ if (!assumeDelivered) {
+ Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
+ + " and " + intent + " without mechanism to finish delivery");
+ }
+ if (sendingUid != Process.INVALID_UID || sendingPackage != null) {
+ Log.wtf(TAG,
+ "scheduleRegisteredReceiver() called for " + receiver + " and " + intent
+ + " from " + sendingPackage + " (UID: " + sendingUid
+ + ") without mechanism to propagate the sender's identity");
+ }
+ receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
+ sendingUser);
+ }
+ }
+
+ @Override
+ public void scheduleLowMemory() {
+ sendMessage(H.LOW_MEMORY, null);
+ }
+
+ @Override
+ public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
+ sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
+ }
+
+ @Override
+ public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
+ ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ DumpHeapData dhd = new DumpHeapData();
+ dhd.managed = managed;
+ dhd.mallocInfo = mallocInfo;
+ dhd.runGc = runGc;
+ dhd.path = path;
+ try {
+ // Since we're going to dump the heap asynchronously, dup the file descriptor before
+ // it's closed on returning from the IPC call.
+ dhd.fd = fd.dup();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to duplicate heap dump file descriptor", e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ dhd.finishCallback = finishCallback;
+ sendMessage(H.DUMP_HEAP, dhd, 0, 0, true /*async*/);
+ }
+
+ public void attachAgent(String agent) {
+ sendMessage(H.ATTACH_AGENT, agent);
+ }
+
+ public void attachStartupAgents(String dataDir) {
+ sendMessage(H.ATTACH_STARTUP_AGENTS, dataDir);
+ }
+
+ public void setSchedulingGroup(int group) {
+ // Note: do this immediately, since going into the foreground
+ // should happen regardless of what pending work we have to do
+ // and the activity manager will wait for us to report back that
+ // we are done before sending us to the background.
+ try {
+ Process.setProcessGroup(Process.myPid(), group);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed setting process group to " + group, e);
+ }
+ }
+
+ public void dispatchPackageBroadcast(int cmd, String[] packages) {
+ sendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
+ }
+
+ @Override
+ public void scheduleCrash(String msg, int typeId, @Nullable Bundle extras) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = msg;
+ args.arg2 = extras;
+ sendMessage(H.SCHEDULE_CRASH, args, typeId);
+ }
+
+ @Override
+ public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) {
+ DumpResourcesData data = new DumpResourcesData();
+ try {
+ data.fd = fd.dup();
+ data.finishCallback = callback;
+ sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpResources failed", e);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
+ public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
+ String prefix, String[] args) {
+ DumpComponentInfo data = new DumpComponentInfo();
+ try {
+ data.fd = pfd.dup();
+ data.token = activitytoken;
+ data.prefix = prefix;
+ data.args = args;
+ sendMessage(H.DUMP_ACTIVITY, data, 0, 0, true /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpActivity failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ public void dumpProvider(ParcelFileDescriptor pfd, IBinder providertoken,
+ String[] args) {
+ DumpComponentInfo data = new DumpComponentInfo();
+ try {
+ data.fd = pfd.dup();
+ data.token = providertoken;
+ data.args = args;
+ sendMessage(H.DUMP_PROVIDER, data, 0, 0, true /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpProvider failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ @Override
+ public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
+ boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+ boolean dumpUnreachable, String[] args) {
+ FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
+ PrintWriter pw = new FastPrintWriter(fout);
+ try {
+ dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+ } finally {
+ pw.flush();
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
+ boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) {
+ long nativeMax = Debug.getNativeHeapSize() / 1024;
+ long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+ long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+ Runtime runtime = Runtime.getRuntime();
+ runtime.gc(); // Do GC since countInstancesOfClass counts unreachable objects.
+ long dalvikMax = runtime.totalMemory() / 1024;
+ long dalvikFree = runtime.freeMemory() / 1024;
+ long dalvikAllocated = dalvikMax - dalvikFree;
+
+ Class[] classesToCount = new Class[] {
+ ContextImpl.class,
+ Activity.class,
+ WebView.class,
+ View.class,
+ ViewRootImpl.class
+ };
+ long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
+ long appContextInstanceCount = instanceCounts[0];
+ long activityInstanceCount = instanceCounts[1];
+ long webviewInstanceCount = instanceCounts[2];
+ long viewInstanceCount = instanceCounts[3];
+ long viewRootInstanceCount = instanceCounts[4];
+
+ int globalAssetCount = AssetManager.getGlobalAssetCount();
+ int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+ int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+ int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+ int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+ long parcelSize = Parcel.getGlobalAllocSize();
+ long parcelCount = Parcel.getGlobalAllocCount();
+ SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
+
+ dumpMemInfoTable(pw, memInfo, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
+ Process.myPid(),
+ (mBoundApplication != null) ? mBoundApplication.processName : "unknown",
+ nativeMax, nativeAllocated, nativeFree,
+ dalvikMax, dalvikAllocated, dalvikFree);
+
+ if (checkin) {
+ // NOTE: if you change anything significant below, also consider changing
+ // ACTIVITY_THREAD_CHECKIN_VERSION.
+
+ // Object counts
+ pw.print(viewInstanceCount); pw.print(',');
+ pw.print(viewRootInstanceCount); pw.print(',');
+ pw.print(appContextInstanceCount); pw.print(',');
+ pw.print(activityInstanceCount); pw.print(',');
+
+ pw.print(globalAssetCount); pw.print(',');
+ pw.print(globalAssetManagerCount); pw.print(',');
+ pw.print(binderLocalObjectCount); pw.print(',');
+ pw.print(binderProxyObjectCount); pw.print(',');
+
+ pw.print(binderDeathObjectCount); pw.print(',');
+
+ // SQL
+ pw.print(stats.memoryUsed / 1024); pw.print(',');
+ pw.print(stats.memoryUsed / 1024); pw.print(',');
+ pw.print(stats.pageCacheOverflow / 1024); pw.print(',');
+ pw.print(stats.largestMemAlloc / 1024);
+ for (int i = 0; i < stats.dbStats.size(); i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ pw.print(','); pw.print(dbStats.dbName);
+ pw.print(','); pw.print(dbStats.pageSize);
+ pw.print(','); pw.print(dbStats.dbSize);
+ pw.print(','); pw.print(dbStats.lookaside);
+ pw.print(','); pw.print(dbStats.cacheHits);
+ pw.print(','); pw.print(dbStats.cacheMisses);
+ pw.print(','); pw.print(dbStats.cacheSize);
+ }
+ pw.println();
+
+ return;
+ }
+
+ pw.println(" ");
+ pw.println(" Objects");
+ printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRootImpl:",
+ viewRootInstanceCount);
+
+ printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount,
+ "Activities:", activityInstanceCount);
+
+ printRow(pw, TWO_COUNT_COLUMNS, "Assets:", globalAssetCount,
+ "AssetManagers:", globalAssetManagerCount);
+
+ printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount,
+ "Proxy Binders:", binderProxyObjectCount);
+ printRow(pw, TWO_COUNT_COLUMNS, "Parcel memory:", parcelSize/1024,
+ "Parcel count:", parcelCount);
+ printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount,
+ "WebViews:", webviewInstanceCount);
+
+ // SQLite mem info
+ pw.println(" ");
+ pw.println(" SQL");
+ printRow(pw, ONE_COUNT_COLUMN, "MEMORY_USED:", stats.memoryUsed / 1024);
+ printRow(pw, TWO_COUNT_COLUMNS, "PAGECACHE_OVERFLOW:",
+ stats.pageCacheOverflow / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024);
+ pw.println(" ");
+ int N = stats.dbStats.size();
+ if (N > 0) {
+ pw.println(" DATABASES");
+ printRow(pw, DB_CONNECTION_INFO_HEADER, "pgsz", "dbsz", "Lookaside(b)",
+ "cache hits", "cache misses", "cache size", "Dbname");
+ pw.println("PER CONNECTION STATS");
+ for (int i = 0; i < N; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ if (dbStats.arePoolStats) {
+ // these will be printed after
+ continue;
+ }
+ printRow(pw, DB_CONNECTION_INFO_FORMAT,
+ (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ",
+ (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
+ (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
+ dbStats.cacheHits, dbStats.cacheMisses, dbStats.cacheSize,
+ dbStats.dbName);
+ }
+ // Print stats accumulated through all the connections that have existed in the
+ // pool since it was opened.
+ pw.println("POOL STATS");
+ printRow(pw, DB_POOL_INFO_HEADER, "cache hits", "cache misses", "cache size",
+ "Dbname");
+ for (int i = 0; i < N; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ if (!dbStats.arePoolStats) {
+ continue;
+ }
+ printRow(pw, DB_POOL_INFO_FORMAT, dbStats.cacheHits, dbStats.cacheMisses,
+ dbStats.cacheSize, dbStats.dbName);
+ }
+ }
+
+ // Asset details.
+ String assetAlloc = AssetManager.getAssetAllocations();
+ if (assetAlloc != null) {
+ pw.println(" ");
+ pw.println(" Asset Allocations");
+ pw.print(assetAlloc);
+ }
+
+ // Unreachable native memory
+ if (dumpUnreachable) {
+ boolean showContents = ((mBoundApplication != null)
+ && ((mBoundApplication.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0))
+ || android.os.Build.IS_DEBUGGABLE;
+ pw.println(" ");
+ pw.println(" Unreachable memory");
+ pw.print(Debug.getUnreachableMemory(100, showContents));
+ }
+ }
+
+ @Override
+ public void dumpMemInfoProto(ParcelFileDescriptor pfd, Debug.MemoryInfo mem,
+ boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+ boolean dumpUnreachable, String[] args) {
+ ProtoOutputStream proto = new ProtoOutputStream(pfd.getFileDescriptor());
+ try {
+ dumpMemInfo(proto, mem, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+ } finally {
+ proto.flush();
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ private void dumpMemInfo(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
+ boolean dumpFullInfo, boolean dumpDalvik,
+ boolean dumpSummaryOnly, boolean dumpUnreachable) {
+ long nativeMax = Debug.getNativeHeapSize() / 1024;
+ long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+ long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+ Runtime runtime = Runtime.getRuntime();
+ runtime.gc(); // Do GC since countInstancesOfClass counts unreachable objects.
+ long dalvikMax = runtime.totalMemory() / 1024;
+ long dalvikFree = runtime.freeMemory() / 1024;
+ long dalvikAllocated = dalvikMax - dalvikFree;
+
+ Class[] classesToCount = new Class[] {
+ ContextImpl.class,
+ Activity.class,
+ WebView.class,
+ View.class,
+ ViewRootImpl.class
+ };
+ long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
+ long appContextInstanceCount = instanceCounts[0];
+ long activityInstanceCount = instanceCounts[1];
+ long webviewInstanceCount = instanceCounts[2];
+ long viewInstanceCount = instanceCounts[3];
+ long viewRootInstanceCount = instanceCounts[4];
+
+ int globalAssetCount = AssetManager.getGlobalAssetCount();
+ int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+ int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+ int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+ int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+ long parcelSize = Parcel.getGlobalAllocSize();
+ long parcelCount = Parcel.getGlobalAllocCount();
+ SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
+
+ final long mToken = proto.start(MemInfoDumpProto.AppData.PROCESS_MEMORY);
+ proto.write(MemInfoDumpProto.ProcessMemory.PID, Process.myPid());
+ proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME,
+ (mBoundApplication != null) ? mBoundApplication.processName : "unknown");
+ dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly,
+ nativeMax, nativeAllocated, nativeFree,
+ dalvikMax, dalvikAllocated, dalvikFree);
+ proto.end(mToken);
+
+ final long oToken = proto.start(MemInfoDumpProto.AppData.OBJECTS);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT,
+ viewInstanceCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
+ viewRootInstanceCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
+ appContextInstanceCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
+ activityInstanceCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT,
+ globalAssetCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
+ globalAssetManagerCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
+ binderLocalObjectCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT,
+ binderProxyObjectCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_MEMORY_KB,
+ parcelSize / 1024);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT,
+ binderDeathObjectCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT,
+ webviewInstanceCount);
+ proto.end(oToken);
+
+ // SQLite mem info
+ final long sToken = proto.start(MemInfoDumpProto.AppData.SQL);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.MEMORY_USED_KB,
+ stats.memoryUsed / 1024);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB,
+ stats.pageCacheOverflow / 1024);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.MALLOC_SIZE_KB,
+ stats.largestMemAlloc / 1024);
+ int n = stats.dbStats.size();
+ for (int i = 0; i < n; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+
+ final long dToken = proto.start(MemInfoDumpProto.AppData.SqlStats.DATABASES);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.NAME, dbStats.dbName);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B,
+ dbStats.lookaside);
+ proto.write(
+ MemInfoDumpProto.AppData.SqlStats.Database.CACHE_HITS, dbStats.cacheHits);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE_MISSES,
+ dbStats.cacheMisses);
+ proto.write(
+ MemInfoDumpProto.AppData.SqlStats.Database.CACHE_SIZE, dbStats.cacheSize);
+ proto.end(dToken);
+ }
+ proto.end(sToken);
+
+ // Asset details.
+ String assetAlloc = AssetManager.getAssetAllocations();
+ if (assetAlloc != null) {
+ proto.write(MemInfoDumpProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
+ }
+
+ // Unreachable native memory
+ if (dumpUnreachable) {
+ int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags;
+ boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
+ || android.os.Build.IS_DEBUGGABLE;
+ proto.write(MemInfoDumpProto.AppData.UNREACHABLE_MEMORY,
+ Debug.getUnreachableMemory(100, showContents));
+ }
+ }
+
+ @Override
+ public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) {
+ DumpComponentInfo data = new DumpComponentInfo();
+ try {
+ data.fd = pfd.dup();
+ data.token = null;
+ data.args = args;
+ sendMessage(H.DUMP_GFXINFO, data, 0, 0, true /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpGfxInfo failed", e);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ @Override
+ public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
+ PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+ IoUtils.closeQuietly(pfd);
+ }
+
+ private File getDatabasesDir(Context context) {
+ // There's no simple way to get the databases/ path, so do it this way.
+ return context.getDatabasePath("a").getParentFile();
+ }
+
+ private void dumpDatabaseInfo(ParcelFileDescriptor pfd, String[] args, boolean isSystem) {
+ PrintWriter pw = new FastPrintWriter(
+ new FileOutputStream(pfd.getFileDescriptor()));
+ PrintWriterPrinter printer = new PrintWriterPrinter(pw);
+ SQLiteDebug.dump(printer, args, isSystem);
+ pw.flush();
+ }
+
+ @Override
+ public void dumpDbInfo(final ParcelFileDescriptor pfd, final String[] args) {
+ if (mSystemThread) {
+ // Ensure this invocation is asynchronous to prevent writer waiting if buffer cannot
+ // be consumed. But it must duplicate the file descriptor first, since caller might
+ // be closing it.
+ final ParcelFileDescriptor dup;
+ try {
+ dup = pfd.dup();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not dup FD " + pfd.getFileDescriptor().getInt$());
+ return;
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ dumpDatabaseInfo(dup, args, true);
+ } finally {
+ IoUtils.closeQuietly(dup);
+ }
+ }
+ });
+ } else {
+ dumpDatabaseInfo(pfd, args, false);
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ @Override
+ public void unstableProviderDied(IBinder provider) {
+ sendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
+ }
+
+ @Override
+ public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
+ int requestType, int sessionId, int flags) {
+ RequestAssistContextExtras cmd = new RequestAssistContextExtras();
+ cmd.activityToken = activityToken;
+ cmd.requestToken = requestToken;
+ cmd.requestType = requestType;
+ cmd.sessionId = sessionId;
+ cmd.flags = flags;
+ sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
+ }
+
+ public void setCoreSettings(Bundle coreSettings) {
+ sendMessage(H.SET_CORE_SETTINGS, coreSettings);
+ }
+
+ public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) {
+ UpdateCompatibilityData ucd = new UpdateCompatibilityData();
+ ucd.pkg = pkg;
+ ucd.info = info;
+ updateCompatOverrideScale(info);
+ sendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
+ }
+
+ public void scheduleTrimMemory(int level) {
+ final Runnable r = PooledLambda.obtainRunnable(ActivityThread::handleTrimMemory,
+ ActivityThread.this, level).recycleOnUse();
+ // Schedule trimming memory after drawing the frame to minimize jank-risk.
+ Choreographer choreographer = Choreographer.getMainThreadInstance();
+ if (choreographer != null) {
+ choreographer.postCallback(Choreographer.CALLBACK_COMMIT, r, null);
+ } else {
+ mH.post(r);
+ }
+ }
+
+ public void scheduleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
+ sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
+ }
+
+ public void scheduleOnNewActivityOptions(IBinder token, Bundle options) {
+ sendMessage(H.ON_NEW_ACTIVITY_OPTIONS,
+ new Pair<IBinder, ActivityOptions>(token, ActivityOptions.fromBundle(options)));
+ }
+
+ public void setProcessState(int state) {
+ updateProcessState(state, true);
+ }
+
+ /**
+ * Updates {@link #mNetworkBlockSeq}. This is used by ActivityManagerService to inform
+ * the main thread that it needs to wait for the network rules to get updated before
+ * launching an activity.
+ */
+ @Override
+ public void setNetworkBlockSeq(long procStateSeq) {
+ synchronized (mNetworkPolicyLock) {
+ mNetworkBlockSeq = procStateSeq;
+ }
+ }
+
+ @Override
+ public void scheduleInstallProvider(ProviderInfo provider) {
+ sendMessage(H.INSTALL_PROVIDER, provider);
+ }
+
+ @Override
+ public final void updateTimePrefs(int timeFormatPreference) {
+ final Boolean timeFormatPreferenceBool;
+ // For convenience we are using the Intent extra values.
+ if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR) {
+ timeFormatPreferenceBool = Boolean.FALSE;
+ } else if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR) {
+ timeFormatPreferenceBool = Boolean.TRUE;
+ } else {
+ // timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT
+ // (or unknown).
+ timeFormatPreferenceBool = null;
+ }
+ DateFormat.set24HourTimePref(timeFormatPreferenceBool);
+ }
+
+ @Override
+ public void scheduleEnterAnimationComplete(IBinder token) {
+ sendMessage(H.ENTER_ANIMATION_COMPLETE, token);
+ }
+
+ @Override
+ public void notifyCleartextNetwork(byte[] firstPacket) {
+ if (StrictMode.vmCleartextNetworkEnabled()) {
+ StrictMode.onCleartextNetworkDetected(firstPacket);
+ }
+ }
+
+ @Override
+ public void startBinderTracking() {
+ sendMessage(H.START_BINDER_TRACKING, null);
+ }
+
+ @Override
+ public void stopBinderTrackingAndDump(ParcelFileDescriptor pfd) {
+ try {
+ sendMessage(H.STOP_BINDER_TRACKING_AND_DUMP, pfd.dup());
+ } catch (IOException e) {
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ @Override
+ public void scheduleLocalVoiceInteractionStarted(IBinder token,
+ IVoiceInteractor voiceInteractor) throws RemoteException {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = token;
+ args.arg2 = voiceInteractor;
+ sendMessage(H.LOCAL_VOICE_INTERACTION_STARTED, args);
+ }
+
+ @Override
+ public void handleTrustStorageUpdate() {
+ NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
+ }
+
+ @Override
+ public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ ActivityThread.this.scheduleTransaction(transaction);
+ }
+
+ @Override
+ public void requestDirectActions(@NonNull IBinder activityToken,
+ @NonNull IVoiceInteractor interactor, @Nullable RemoteCallback cancellationCallback,
+ @NonNull RemoteCallback callback) {
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ if (cancellationCallback != null) {
+ final ICancellationSignal transport = createSafeCancellationTransport(
+ cancellationSignal);
+ final Bundle cancellationResult = new Bundle();
+ cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
+ transport.asBinder());
+ cancellationCallback.sendResult(cancellationResult);
+ }
+ mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
+ ActivityThread.this, activityToken, interactor, cancellationSignal, callback,
+ REQUEST_DIRECT_ACTIONS_RETRY_MAX_COUNT));
+ }
+
+ @Override
+ public void performDirectAction(@NonNull IBinder activityToken, @NonNull String actionId,
+ @Nullable Bundle arguments, @Nullable RemoteCallback cancellationCallback,
+ @NonNull RemoteCallback resultCallback) {
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ if (cancellationCallback != null) {
+ final ICancellationSignal transport = createSafeCancellationTransport(
+ cancellationSignal);
+ final Bundle cancellationResult = new Bundle();
+ cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
+ transport.asBinder());
+ cancellationCallback.sendResult(cancellationResult);
+ }
+ mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handlePerformDirectAction,
+ ActivityThread.this, activityToken, actionId, arguments,
+ cancellationSignal, resultCallback));
+ }
+
+ @Override
+ public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder,
+ @NonNull String authorities, int userId, boolean published) {
+ final String auths[] = authorities.split(";");
+ for (String auth: auths) {
+ final ProviderKey key = getGetProviderKey(auth, userId);
+ synchronized (key.mLock) {
+ key.mHolder = holder;
+ key.mLock.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void instrumentWithoutRestart(ComponentName instrumentationName,
+ Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
+ IUiAutomationConnection instrumentationUiConnection, ApplicationInfo targetInfo) {
+ AppBindData data = new AppBindData();
+ data.instrumentationName = instrumentationName;
+ data.instrumentationArgs = instrumentationArgs;
+ data.instrumentationWatcher = instrumentationWatcher;
+ data.instrumentationUiAutomationConnection = instrumentationUiConnection;
+ data.appInfo = targetInfo;
+ sendMessage(H.INSTRUMENT_WITHOUT_RESTART, data);
+ }
+
+ @Override
+ public void updateUiTranslationState(IBinder activityToken, int state,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
+ UiTranslationSpec uiTranslationSpec) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = activityToken;
+ args.arg2 = state;
+ args.arg3 = sourceSpec;
+ args.arg4 = targetSpec;
+ args.arg5 = viewIds;
+ args.arg6 = uiTranslationSpec;
+ sendMessage(H.UPDATE_UI_TRANSLATION_STATE, args);
+ }
+ }
+
+ private @NonNull SafeCancellationTransport createSafeCancellationTransport(
+ @NonNull CancellationSignal cancellationSignal) {
+ synchronized (ActivityThread.this) {
+ if (mRemoteCancellations == null) {
+ mRemoteCancellations = new ArrayMap<>();
+ }
+ final SafeCancellationTransport transport = new SafeCancellationTransport(
+ this, cancellationSignal);
+ mRemoteCancellations.put(transport, cancellationSignal);
+ return transport;
+ }
+ }
+
+ private @NonNull CancellationSignal removeSafeCancellationTransport(
+ @NonNull SafeCancellationTransport transport) {
+ synchronized (ActivityThread.this) {
+ final CancellationSignal cancellation = mRemoteCancellations.remove(transport);
+ if (mRemoteCancellations.isEmpty()) {
+ mRemoteCancellations = null;
+ }
+ return cancellation;
+ }
+ }
+
+ private static final class SafeCancellationTransport extends ICancellationSignal.Stub {
+ private final @NonNull WeakReference<ActivityThread> mWeakActivityThread;
+
+ SafeCancellationTransport(@NonNull ActivityThread activityThread,
+ @NonNull CancellationSignal cancellation) {
+ mWeakActivityThread = new WeakReference<>(activityThread);
+ }
+
+ @Override
+ public void cancel() {
+ final ActivityThread activityThread = mWeakActivityThread.get();
+ if (activityThread != null) {
+ final CancellationSignal cancellation = activityThread
+ .removeSafeCancellationTransport(this);
+ if (cancellation != null) {
+ cancellation.cancel();
+ }
+ }
+ }
+ }
+
+ private void throwRemoteServiceException(String message, int typeId, @Nullable Bundle extras) {
+ // Use a switch to ensure all the type IDs are unique.
+ switch (typeId) {
+ case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
+ throw generateForegroundServiceDidNotStartInTimeException(message, extras);
+
+ case CannotPostForegroundServiceNotificationException.TYPE_ID:
+ throw new CannotPostForegroundServiceNotificationException(message);
+
+ case BadForegroundServiceNotificationException.TYPE_ID:
+ throw new BadForegroundServiceNotificationException(message);
+
+ case BadUserInitiatedJobNotificationException.TYPE_ID:
+ throw new BadUserInitiatedJobNotificationException(message);
+
+ case MissingRequestPasswordComplexityPermissionException.TYPE_ID:
+ throw new MissingRequestPasswordComplexityPermissionException(message);
+
+ case CrashedByAdbException.TYPE_ID:
+ throw new CrashedByAdbException(message);
+
+ default:
+ throw new RemoteServiceException(message
+ + " (with unwknown typeId:" + typeId + ")");
+ }
+ }
+
+ private ForegroundServiceDidNotStartInTimeException
+ generateForegroundServiceDidNotStartInTimeException(String message, Bundle extras) {
+ final String serviceClassName =
+ ForegroundServiceDidNotStartInTimeException.getServiceClassNameFromExtras(extras);
+ final Exception inner = (serviceClassName == null) ? null
+ : Service.getStartForegroundServiceStackTrace(serviceClassName);
+ throw new ForegroundServiceDidNotStartInTimeException(message, inner);
+ }
+
+ class H extends Handler {
+ public static final int BIND_APPLICATION = 110;
+ @UnsupportedAppUsage
+ public static final int EXIT_APPLICATION = 111;
+ @UnsupportedAppUsage
+ public static final int RECEIVER = 113;
+ @UnsupportedAppUsage
+ public static final int CREATE_SERVICE = 114;
+ @UnsupportedAppUsage
+ public static final int SERVICE_ARGS = 115;
+ @UnsupportedAppUsage
+ public static final int STOP_SERVICE = 116;
+
+ public static final int CONFIGURATION_CHANGED = 118;
+ public static final int CLEAN_UP_CONTEXT = 119;
+ @UnsupportedAppUsage
+ public static final int GC_WHEN_IDLE = 120;
+ @UnsupportedAppUsage
+ public static final int BIND_SERVICE = 121;
+ @UnsupportedAppUsage
+ public static final int UNBIND_SERVICE = 122;
+ public static final int DUMP_SERVICE = 123;
+ public static final int LOW_MEMORY = 124;
+ public static final int PROFILER_CONTROL = 127;
+ public static final int CREATE_BACKUP_AGENT = 128;
+ public static final int DESTROY_BACKUP_AGENT = 129;
+ public static final int SUICIDE = 130;
+ @UnsupportedAppUsage
+ public static final int REMOVE_PROVIDER = 131;
+ public static final int DISPATCH_PACKAGE_BROADCAST = 133;
+ @UnsupportedAppUsage
+ public static final int SCHEDULE_CRASH = 134;
+ public static final int DUMP_HEAP = 135;
+ public static final int DUMP_ACTIVITY = 136;
+ public static final int SLEEPING = 137;
+ public static final int SET_CORE_SETTINGS = 138;
+ public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
+ @UnsupportedAppUsage
+ public static final int DUMP_PROVIDER = 141;
+ public static final int UNSTABLE_PROVIDER_DIED = 142;
+ public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
+ public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
+ @UnsupportedAppUsage
+ public static final int INSTALL_PROVIDER = 145;
+ public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
+ @UnsupportedAppUsage
+ public static final int ENTER_ANIMATION_COMPLETE = 149;
+ public static final int START_BINDER_TRACKING = 150;
+ public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
+ public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
+ public static final int ATTACH_AGENT = 155;
+ public static final int APPLICATION_INFO_CHANGED = 156;
+ public static final int RUN_ISOLATED_ENTRY_POINT = 158;
+ public static final int EXECUTE_TRANSACTION = 159;
+ public static final int RELAUNCH_ACTIVITY = 160;
+ public static final int PURGE_RESOURCES = 161;
+ public static final int ATTACH_STARTUP_AGENTS = 162;
+ public static final int UPDATE_UI_TRANSLATION_STATE = 163;
+ public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
+ public static final int DUMP_GFXINFO = 165;
+ public static final int DUMP_RESOURCES = 166;
+ public static final int TIMEOUT_SERVICE = 167;
+ public static final int PING = 168;
+
+ public static final int INSTRUMENT_WITHOUT_RESTART = 170;
+ public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
+
+ String codeToString(int code) {
+ if (DEBUG_MESSAGES) {
+ switch (code) {
+ case BIND_APPLICATION: return "BIND_APPLICATION";
+ case EXIT_APPLICATION: return "EXIT_APPLICATION";
+ case RECEIVER: return "RECEIVER";
+ case CREATE_SERVICE: return "CREATE_SERVICE";
+ case SERVICE_ARGS: return "SERVICE_ARGS";
+ case STOP_SERVICE: return "STOP_SERVICE";
+ case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED";
+ case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT";
+ case GC_WHEN_IDLE: return "GC_WHEN_IDLE";
+ case BIND_SERVICE: return "BIND_SERVICE";
+ case UNBIND_SERVICE: return "UNBIND_SERVICE";
+ case DUMP_SERVICE: return "DUMP_SERVICE";
+ case LOW_MEMORY: return "LOW_MEMORY";
+ case PROFILER_CONTROL: return "PROFILER_CONTROL";
+ case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
+ case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
+ case SUICIDE: return "SUICIDE";
+ case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
+ case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
+ case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
+ case DUMP_HEAP: return "DUMP_HEAP";
+ case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
+ case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
+ case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
+ case DUMP_PROVIDER: return "DUMP_PROVIDER";
+ case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
+ case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
+ case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE";
+ case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
+ case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
+ case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
+ case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
+ case ATTACH_AGENT: return "ATTACH_AGENT";
+ case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
+ case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
+ case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
+ case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
+ case PURGE_RESOURCES: return "PURGE_RESOURCES";
+ case ATTACH_STARTUP_AGENTS: return "ATTACH_STARTUP_AGENTS";
+ case UPDATE_UI_TRANSLATION_STATE: return "UPDATE_UI_TRANSLATION_STATE";
+ case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK:
+ return "SET_CONTENT_CAPTURE_OPTIONS_CALLBACK";
+ case DUMP_GFXINFO: return "DUMP GFXINFO";
+ case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
+ case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
+ return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+ case DUMP_RESOURCES: return "DUMP_RESOURCES";
+ case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
+ case PING: return "PING";
+ }
+ }
+ return Integer.toString(code);
+ }
+ public void handleMessage(Message msg) {
+ if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
+ switch (msg.what) {
+ case BIND_APPLICATION:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
+ AppBindData data = (AppBindData)msg.obj;
+ handleBindApplication(data);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case EXIT_APPLICATION:
+ if (mInitialApplication != null) {
+ mInitialApplication.onTerminate();
+ }
+ Looper.myLooper().quit();
+ break;
+ case RECEIVER:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ ReceiverData rec = (ReceiverData) msg.obj;
+ if (rec.intent != null) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "broadcastReceiveComp: " + rec.intent.getAction());
+ } else {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "broadcastReceiveComp");
+ }
+ }
+ handleReceiver((ReceiverData)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case CREATE_SERVICE:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ ("serviceCreate: " + String.valueOf(msg.obj)));
+ }
+ handleCreateService((CreateServiceData)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case BIND_SERVICE:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind: "
+ + String.valueOf(msg.obj));
+ }
+ handleBindService((BindServiceData)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case UNBIND_SERVICE:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind: "
+ + String.valueOf(msg.obj));
+ }
+ handleUnbindService((BindServiceData)msg.obj);
+ schedulePurgeIdler();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case SERVICE_ARGS:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ ("serviceStart: " + String.valueOf(msg.obj)));
+ }
+ handleServiceArgs((ServiceArgsData)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case STOP_SERVICE:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop: "
+ + String.valueOf(msg.obj));
+ }
+ handleStopService((IBinder)msg.obj);
+ schedulePurgeIdler();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case TIMEOUT_SERVICE:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceTimeout: "
+ + String.valueOf(msg.obj));
+ }
+ handleTimeoutService((IBinder) msg.obj, msg.arg1);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case PING:
+ ((RemoteCallback) msg.obj).sendResult(null);
+ break;
+ case CONFIGURATION_CHANGED:
+ mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
+ break;
+ case CLEAN_UP_CONTEXT:
+ ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
+ cci.context.performFinalCleanup(cci.who, cci.what);
+ break;
+ case GC_WHEN_IDLE:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "gcWhenIdle");
+ try {
+ scheduleGcIdler();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ break;
+ case DUMP_SERVICE:
+ handleDumpService((DumpComponentInfo)msg.obj);
+ break;
+ case DUMP_GFXINFO:
+ handleDumpGfxInfo((DumpComponentInfo) msg.obj);
+ break;
+ case LOW_MEMORY:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory");
+ handleLowMemory();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case PROFILER_CONTROL:
+ handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
+ break;
+ case CREATE_BACKUP_AGENT:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent");
+ handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case DESTROY_BACKUP_AGENT:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent");
+ handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case SUICIDE:
+ Process.killProcess(Process.myPid());
+ break;
+ case REMOVE_PROVIDER:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove");
+ completeRemoveProvider((ProviderRefCount)msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case DISPATCH_PACKAGE_BROADCAST:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage");
+ handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case SCHEDULE_CRASH: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ String message = (String) args.arg1;
+ Bundle extras = (Bundle) args.arg2;
+ args.recycle();
+ throwRemoteServiceException(message, msg.arg1, extras);
+ break;
+ }
+ case DUMP_HEAP:
+ handleDumpHeap((DumpHeapData) msg.obj);
+ break;
+ case DUMP_RESOURCES:
+ handleDumpResources((DumpResourcesData) msg.obj);
+ break;
+ case DUMP_ACTIVITY:
+ handleDumpActivity((DumpComponentInfo)msg.obj);
+ break;
+ case DUMP_PROVIDER:
+ handleDumpProvider((DumpComponentInfo)msg.obj);
+ break;
+ case SET_CORE_SETTINGS:
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings");
+ handleSetCoreSettings((Bundle) msg.obj);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ break;
+ case UPDATE_PACKAGE_COMPATIBILITY_INFO:
+ handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj);
+ break;
+ case UNSTABLE_PROVIDER_DIED:
+ handleUnstableProviderDied((IBinder)msg.obj, false);
+ break;
+ case REQUEST_ASSIST_CONTEXT_EXTRAS:
+ handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj);
+ break;
+ case TRANSLUCENT_CONVERSION_COMPLETE:
+ handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1);
+ break;
+ case INSTALL_PROVIDER:
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerInstall: "
+ + String.valueOf(msg.obj));
+ }
+ try {
+ handleInstallProvider((ProviderInfo) msg.obj);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ break;
+ case ON_NEW_ACTIVITY_OPTIONS:
+ Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj;
+ onNewActivityOptions(pair.first, pair.second);
+ break;
+ case ENTER_ANIMATION_COMPLETE:
+ handleEnterAnimationComplete((IBinder) msg.obj);
+ break;
+ case START_BINDER_TRACKING:
+ handleStartBinderTracking();
+ break;
+ case STOP_BINDER_TRACKING_AND_DUMP:
+ handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
+ break;
+ case LOCAL_VOICE_INTERACTION_STARTED:
+ handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
+ (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
+ break;
+ case ATTACH_AGENT: {
+ Application app = getApplication();
+ handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null);
+ break;
+ }
+ case APPLICATION_INFO_CHANGED:
+ handleApplicationInfoChanged((ApplicationInfo) msg.obj);
+ break;
+ case RUN_ISOLATED_ENTRY_POINT:
+ handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
+ (String[]) ((SomeArgs) msg.obj).arg2);
+ break;
+ case EXECUTE_TRANSACTION:
+ final ClientTransaction transaction = (ClientTransaction) msg.obj;
+ mTransactionExecutor.execute(transaction);
+ if (isSystem()) {
+ // Client transactions inside system process are recycled on the client side
+ // instead of ClientLifecycleManager to avoid being cleared before this
+ // message is handled.
+ transaction.recycle();
+ }
+ // TODO(lifecycler): Recycle locally scheduled transactions.
+ break;
+ case RELAUNCH_ACTIVITY:
+ handleRelaunchActivityLocally((IBinder) msg.obj);
+ break;
+ case PURGE_RESOURCES:
+ schedulePurgeIdler();
+ break;
+ case ATTACH_STARTUP_AGENTS:
+ handleAttachStartupAgents((String) msg.obj);
+ break;
+ case UPDATE_UI_TRANSLATION_STATE:
+ final SomeArgs args = (SomeArgs) msg.obj;
+ updateUiTranslationState((IBinder) args.arg1, (int) args.arg2,
+ (TranslationSpec) args.arg3, (TranslationSpec) args.arg4,
+ (List<AutofillId>) args.arg5, (UiTranslationSpec) args.arg6);
+ break;
+ case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK:
+ handleSetContentCaptureOptionsCallback((String) msg.obj);
+ break;
+ case INSTRUMENT_WITHOUT_RESTART:
+ handleInstrumentWithoutRestart((AppBindData) msg.obj);
+ break;
+ case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
+ handleFinishInstrumentationWithoutRestart();
+ break;
+ }
+ Object obj = msg.obj;
+ if (obj instanceof SomeArgs) {
+ ((SomeArgs) obj).recycle();
+ }
+ if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
+ }
+ }
+
+ private class Idler implements MessageQueue.IdleHandler {
+ @Override
+ public final boolean queueIdle() {
+ boolean stopProfiling = false;
+ if (mBoundApplication != null && mProfiler.profileFd != null
+ && mProfiler.autoStopProfiler) {
+ stopProfiling = true;
+ }
+ final ActivityClient ac = ActivityClient.getInstance();
+ while (mNewActivities.size() > 0) {
+ final ActivityClientRecord a = mNewActivities.remove(0);
+ if (localLOGV) {
+ Slog.v(TAG, "Reporting idle of " + a + " finished="
+ + (a.activity != null && a.activity.mFinished));
+ }
+ if (a.activity != null && !a.activity.mFinished) {
+ ac.activityIdle(a.token, a.createdConfig, stopProfiling);
+ a.createdConfig = null;
+ }
+ }
+ if (stopProfiling) {
+ mProfiler.stopProfiling();
+ }
+ return false;
+ }
+ }
+
+ final class GcIdler implements MessageQueue.IdleHandler {
+ @Override
+ public final boolean queueIdle() {
+ doGcIfNeeded();
+ purgePendingResources();
+ return false;
+ }
+ }
+
+ final class PurgeIdler implements MessageQueue.IdleHandler {
+ @Override
+ public boolean queueIdle() {
+ purgePendingResources();
+ return false;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static ActivityThread currentActivityThread() {
+ return sCurrentActivityThread;
+ }
+
+ public static boolean isSystem() {
+ return (sCurrentActivityThread != null) ? sCurrentActivityThread.mSystemThread : false;
+ }
+
+ public static String currentOpPackageName() {
+ ActivityThread am = currentActivityThread();
+ return (am != null && am.getApplication() != null)
+ ? am.getApplication().getOpPackageName() : null;
+ }
+
+ public static AttributionSource currentAttributionSource() {
+ ActivityThread am = currentActivityThread();
+ return (am != null && am.getApplication() != null)
+ ? am.getApplication().getAttributionSource() : null;
+ }
+
+ @UnsupportedAppUsage
+ public static String currentPackageName() {
+ ActivityThread am = currentActivityThread();
+ return (am != null && am.mBoundApplication != null)
+ ? am.mBoundApplication.appInfo.packageName : null;
+ }
+
+ @UnsupportedAppUsage
+ public static String currentProcessName() {
+ ActivityThread am = currentActivityThread();
+ return (am != null && am.mBoundApplication != null)
+ ? am.mBoundApplication.processName : null;
+ }
+
+ @UnsupportedAppUsage
+ public static Application currentApplication() {
+ ActivityThread am = currentActivityThread();
+ return am != null ? am.mInitialApplication : null;
+ }
+
+ @UnsupportedAppUsage
+ public static IPackageManager getPackageManager() {
+ if (sPackageManager != null) {
+ return sPackageManager;
+ }
+ final IBinder b = ServiceManager.getService("package");
+ sPackageManager = IPackageManager.Stub.asInterface(b);
+ return sPackageManager;
+ }
+
+ /** Returns the permission manager */
+ public static IPermissionManager getPermissionManager() {
+ if (sPermissionManager != null) {
+ return sPermissionManager;
+ }
+ final IBinder b = ServiceManager.getService("permissionmgr");
+ sPermissionManager = IPermissionManager.Stub.asInterface(b);
+ return sPermissionManager;
+ }
+
+ /**
+ * Creates the top level resources for the given package. Will return an existing
+ * Resources if one has already been created.
+ */
+ Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] legacyOverlayDirs,
+ String[] overlayPaths, String[] libDirs, LoadedApk pkgInfo,
+ Configuration overrideConfig) {
+ return mResourcesManager.getResources(null, resDir, splitResDirs, legacyOverlayDirs,
+ overlayPaths, libDirs, null, overrideConfig, pkgInfo.getCompatibilityInfo(),
+ pkgInfo.getClassLoader(), null);
+ }
+
+ @UnsupportedAppUsage
+ public Handler getHandler() {
+ return mH;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+ int flags) {
+ return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
+ }
+
+ public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+ int flags, int userId) {
+ final boolean differentUser = (UserHandle.myUserId() != userId);
+ ApplicationInfo ai = PackageManager.getApplicationInfoAsUserCached(
+ packageName,
+ PackageManager.GET_SHARED_LIBRARY_FILES
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ (userId < 0) ? UserHandle.myUserId() : userId);
+ synchronized (mResourcesManager) {
+ WeakReference<LoadedApk> ref;
+ if (differentUser) {
+ // Caching not supported across users
+ ref = null;
+ } else if ((flags & Context.CONTEXT_INCLUDE_CODE) != 0) {
+ ref = mPackages.get(packageName);
+ } else {
+ ref = mResourcePackages.get(packageName);
+ }
+
+ LoadedApk packageInfo = ref != null ? ref.get() : null;
+ if (ai != null && packageInfo != null) {
+ if (!isLoadedApkResourceDirsUpToDate(packageInfo, ai)) {
+ List<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, ai, oldPaths);
+ packageInfo.updateApplicationInfo(ai, oldPaths);
+ }
+
+ if (packageInfo.isSecurityViolation()
+ && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) {
+ throw new SecurityException(
+ "Requesting code from " + packageName
+ + " to be run in process "
+ + mBoundApplication.processName
+ + "/" + mBoundApplication.appInfo.uid);
+ }
+ return packageInfo;
+ }
+ }
+
+ if (ai != null) {
+ return getPackageInfo(ai, compatInfo, flags);
+ }
+
+ return null;
+ }
+
+ @UnsupportedAppUsage(trackingBug = 171933273)
+ public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
+ int flags) {
+ boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
+ boolean securityViolation = includeCode && ai.uid != 0
+ && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
+ ? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
+ : true);
+ boolean registerPackage = includeCode && (flags&Context.CONTEXT_REGISTER_PACKAGE) != 0;
+ if ((flags&(Context.CONTEXT_INCLUDE_CODE
+ |Context.CONTEXT_IGNORE_SECURITY))
+ == Context.CONTEXT_INCLUDE_CODE) {
+ if (securityViolation) {
+ String msg = "Requesting code from " + ai.packageName
+ + " (with uid " + ai.uid + ")";
+ if (mBoundApplication != null) {
+ msg = msg + " to be run in process "
+ + mBoundApplication.processName + " (with uid "
+ + mBoundApplication.appInfo.uid + ")";
+ }
+ throw new SecurityException(msg);
+ }
+ }
+ return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode,
+ registerPackage);
+ }
+
+ @UnsupportedAppUsage
+ public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
+ CompatibilityInfo compatInfo) {
+ return getPackageInfo(ai, compatInfo, null, false, true, false);
+ }
+
+ @Override
+ public LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
+ return getPackageInfo(ai, mCompatibilityInfo, null /* baseLoader */,
+ false /* securityViolation */, true /* includeCode */, false /* registerPackage */);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
+ synchronized (mResourcesManager) {
+ WeakReference<LoadedApk> ref;
+ if (includeCode) {
+ ref = mPackages.get(packageName);
+ } else {
+ ref = mResourcePackages.get(packageName);
+ }
+ return ref != null ? ref.get() : null;
+ }
+ }
+
+ private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
+ ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
+ boolean registerPackage) {
+ return getPackageInfo(aInfo, compatInfo, baseLoader, securityViolation, includeCode,
+ registerPackage, Process.isSdkSandbox());
+ }
+
+ private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
+ ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
+ boolean registerPackage, boolean isSdkSandbox) {
+ final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
+ synchronized (mResourcesManager) {
+ WeakReference<LoadedApk> ref;
+ if (differentUser || isSdkSandbox) {
+ // Caching not supported across users and for sdk sandboxes
+ ref = null;
+ } else if (includeCode) {
+ ref = mPackages.get(aInfo.packageName);
+ } else {
+ ref = mResourcePackages.get(aInfo.packageName);
+ }
+
+ LoadedApk packageInfo = ref != null ? ref.get() : null;
+
+ if (packageInfo != null) {
+ if (!isLoadedApkResourceDirsUpToDate(packageInfo, aInfo)) {
+ if (packageInfo.getApplicationInfo().createTimestamp > aInfo.createTimestamp) {
+ // The cached loaded apk is newer than the one passed in, we should not
+ // update the cached version
+ Slog.w(TAG, "getPackageInfo() called with an older ApplicationInfo "
+ + "than the cached version for package " + aInfo.packageName);
+ } else {
+ Slog.v(TAG, "getPackageInfo() caused update to cached ApplicationInfo "
+ + "for package " + aInfo.packageName);
+ List<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, aInfo, oldPaths);
+ packageInfo.updateApplicationInfo(aInfo, oldPaths);
+ }
+ }
+
+ return packageInfo;
+ }
+
+ if (localLOGV) {
+ Slog.v(TAG, (includeCode ? "Loading code package "
+ : "Loading resource-only package ") + aInfo.packageName
+ + " (in " + (mBoundApplication != null
+ ? mBoundApplication.processName : null)
+ + ")");
+ }
+
+ packageInfo =
+ new LoadedApk(this, aInfo, compatInfo, baseLoader,
+ securityViolation, includeCode
+ && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
+
+ if (mSystemThread && "android".equals(aInfo.packageName)) {
+ packageInfo.installSystemApplicationInfo(aInfo,
+ getSystemContext().mPackageInfo.getClassLoader());
+ }
+
+ if (differentUser || isSdkSandbox) {
+ // Caching not supported across users and for sdk sandboxes
+ } else if (includeCode) {
+ mPackages.put(aInfo.packageName,
+ new WeakReference<LoadedApk>(packageInfo));
+ } else {
+ mResourcePackages.put(aInfo.packageName,
+ new WeakReference<LoadedApk>(packageInfo));
+ }
+
+ return packageInfo;
+ }
+ }
+
+ private static boolean isLoadedApkResourceDirsUpToDate(LoadedApk loadedApk,
+ ApplicationInfo appInfo) {
+ Resources packageResources = loadedApk.mResources;
+ boolean resourceDirsUpToDate = Arrays.equals(
+ ArrayUtils.defeatNullable(appInfo.resourceDirs),
+ ArrayUtils.defeatNullable(loadedApk.getOverlayDirs()));
+ boolean overlayPathsUpToDate = Arrays.equals(
+ ArrayUtils.defeatNullable(appInfo.overlayPaths),
+ ArrayUtils.defeatNullable(loadedApk.getOverlayPaths()));
+
+ return (packageResources == null || packageResources.getAssets().isUpToDate())
+ && resourceDirsUpToDate && overlayPathsUpToDate;
+ }
+
+ @UnsupportedAppUsage
+ ActivityThread() {
+ mResourcesManager = ResourcesManager.getInstance();
+ }
+
+ @UnsupportedAppUsage
+ public ApplicationThread getApplicationThread()
+ {
+ return mAppThread;
+ }
+
+ @UnsupportedAppUsage
+ public Instrumentation getInstrumentation()
+ {
+ return mInstrumentation;
+ }
+
+ public boolean isProfiling() {
+ return mProfiler != null && mProfiler.profileFile != null
+ && mProfiler.profileFd == null;
+ }
+
+ public String getProfileFilePath() {
+ return mProfiler.profileFile;
+ }
+
+ @UnsupportedAppUsage
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ public Executor getExecutor() {
+ return mExecutor;
+ }
+
+ @Override
+ @UnsupportedAppUsage
+ public Application getApplication() {
+ return mInitialApplication;
+ }
+
+ @UnsupportedAppUsage
+ public String getProcessName() {
+ return mBoundApplication.processName;
+ }
+
+ @Override
+ @UnsupportedAppUsage
+ public ContextImpl getSystemContext() {
+ synchronized (this) {
+ if (mSystemContext == null) {
+ mSystemContext = ContextImpl.createSystemContext(this);
+ }
+ return mSystemContext;
+ }
+ }
+
+ @NonNull
+ public ContextImpl getSystemUiContext() {
+ return getSystemUiContext(DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Gets the context instance base on system resources & display information which used for UI.
+ * @param displayId The ID of the display where the UI is shown.
+ * @see ContextImpl#createSystemUiContext(ContextImpl, int)
+ */
+ @NonNull
+ public ContextImpl getSystemUiContext(int displayId) {
+ synchronized (this) {
+ if (mDisplaySystemUiContexts == null) {
+ mDisplaySystemUiContexts = new SparseArray<>();
+ }
+ ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId);
+ if (systemUiContext == null) {
+ systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
+ mDisplaySystemUiContexts.put(displayId, systemUiContext);
+ }
+ return systemUiContext;
+ }
+ }
+
+ @Nullable
+ @Override
+ public ContextImpl getSystemUiContextNoCreate() {
+ synchronized (this) {
+ if (mDisplaySystemUiContexts == null) return null;
+ return mDisplaySystemUiContexts.get(DEFAULT_DISPLAY);
+ }
+ }
+
+ void onSystemUiContextCleanup(ContextImpl context) {
+ synchronized (this) {
+ if (mDisplaySystemUiContexts == null) return;
+ final int index = mDisplaySystemUiContexts.indexOfValue(context);
+ if (index >= 0) {
+ mDisplaySystemUiContexts.removeAt(index);
+ }
+ }
+ }
+
+ public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
+ synchronized (this) {
+ getSystemContext().installSystemApplicationInfo(info, classLoader);
+ getSystemUiContext().installSystemApplicationInfo(info, classLoader);
+
+ // give ourselves a default profiler
+ mProfiler = new Profiler();
+ }
+ }
+
+ @UnsupportedAppUsage
+ void scheduleGcIdler() {
+ if (!mGcIdlerScheduled) {
+ mGcIdlerScheduled = true;
+ Looper.myQueue().addIdleHandler(mGcIdler);
+ }
+ mH.removeMessages(H.GC_WHEN_IDLE);
+ }
+
+ void unscheduleGcIdler() {
+ if (mGcIdlerScheduled) {
+ mGcIdlerScheduled = false;
+ Looper.myQueue().removeIdleHandler(mGcIdler);
+ }
+ mH.removeMessages(H.GC_WHEN_IDLE);
+ }
+
+ void schedulePurgeIdler() {
+ if (!mPurgeIdlerScheduled) {
+ mPurgeIdlerScheduled = true;
+ Looper.myQueue().addIdleHandler(mPurgeIdler);
+ }
+ mH.removeMessages(H.PURGE_RESOURCES);
+ }
+
+ void unschedulePurgeIdler() {
+ if (mPurgeIdlerScheduled) {
+ mPurgeIdlerScheduled = false;
+ Looper.myQueue().removeIdleHandler(mPurgeIdler);
+ }
+ mH.removeMessages(H.PURGE_RESOURCES);
+ }
+
+ void doGcIfNeeded() {
+ doGcIfNeeded("bg");
+ }
+
+ void doGcIfNeeded(String reason) {
+ mGcIdlerScheduled = false;
+ final long now = SystemClock.uptimeMillis();
+ //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
+ // + "m now=" + now);
+ if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
+ //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
+ BinderInternal.forceGc(reason);
+ }
+ }
+
+ private static final String HEAP_FULL_COLUMN =
+ "%13s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s";
+ private static final String HEAP_COLUMN =
+ "%13s %8s %8s %8s %8s %8s %8s %8s %8s";
+ private static final String ONE_COUNT_COLUMN = "%21s %8d";
+ private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
+ private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8s %21s %8d";
+ private static final String TWO_COUNT_COLUMN_HEADER = "%21s %8s %21s %8s";
+ private static final String ONE_ALT_COUNT_COLUMN = "%21s %8s %21s %8d";
+
+ // Formatting for checkin service - update version if row format changes
+ private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4;
+
+ static void printRow(PrintWriter pw, String format, Object...objs) {
+ pw.println(String.format(format, objs));
+ }
+
+ public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
+ boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+ int pid, String processName,
+ long nativeMax, long nativeAllocated, long nativeFree,
+ long dalvikMax, long dalvikAllocated, long dalvikFree) {
+
+ // For checkin, we print one long comma-separated list of values
+ if (checkin) {
+ // NOTE: if you change anything significant below, also consider changing
+ // ACTIVITY_THREAD_CHECKIN_VERSION.
+
+ // Header
+ pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
+ pw.print(pid); pw.print(',');
+ pw.print(processName); pw.print(',');
+
+ // Heap info - max
+ pw.print(nativeMax); pw.print(',');
+ pw.print(dalvikMax); pw.print(',');
+ pw.print("N/A,");
+ pw.print(nativeMax + dalvikMax); pw.print(',');
+
+ // Heap info - allocated
+ pw.print(nativeAllocated); pw.print(',');
+ pw.print(dalvikAllocated); pw.print(',');
+ pw.print("N/A,");
+ pw.print(nativeAllocated + dalvikAllocated); pw.print(',');
+
+ // Heap info - free
+ pw.print(nativeFree); pw.print(',');
+ pw.print(dalvikFree); pw.print(',');
+ pw.print("N/A,");
+ pw.print(nativeFree + dalvikFree); pw.print(',');
+
+ // Heap info - proportional set size
+ pw.print(memInfo.nativePss); pw.print(',');
+ pw.print(memInfo.dalvikPss); pw.print(',');
+ pw.print(memInfo.otherPss); pw.print(',');
+ pw.print(memInfo.getTotalPss()); pw.print(',');
+
+ // Heap info - swappable set size
+ pw.print(memInfo.nativeSwappablePss); pw.print(',');
+ pw.print(memInfo.dalvikSwappablePss); pw.print(',');
+ pw.print(memInfo.otherSwappablePss); pw.print(',');
+ pw.print(memInfo.getTotalSwappablePss()); pw.print(',');
+
+ // Heap info - shared dirty
+ pw.print(memInfo.nativeSharedDirty); pw.print(',');
+ pw.print(memInfo.dalvikSharedDirty); pw.print(',');
+ pw.print(memInfo.otherSharedDirty); pw.print(',');
+ pw.print(memInfo.getTotalSharedDirty()); pw.print(',');
+
+ // Heap info - shared clean
+ pw.print(memInfo.nativeSharedClean); pw.print(',');
+ pw.print(memInfo.dalvikSharedClean); pw.print(',');
+ pw.print(memInfo.otherSharedClean); pw.print(',');
+ pw.print(memInfo.getTotalSharedClean()); pw.print(',');
+
+ // Heap info - private Dirty
+ pw.print(memInfo.nativePrivateDirty); pw.print(',');
+ pw.print(memInfo.dalvikPrivateDirty); pw.print(',');
+ pw.print(memInfo.otherPrivateDirty); pw.print(',');
+ pw.print(memInfo.getTotalPrivateDirty()); pw.print(',');
+
+ // Heap info - private Clean
+ pw.print(memInfo.nativePrivateClean); pw.print(',');
+ pw.print(memInfo.dalvikPrivateClean); pw.print(',');
+ pw.print(memInfo.otherPrivateClean); pw.print(',');
+ pw.print(memInfo.getTotalPrivateClean()); pw.print(',');
+
+ // Heap info - swapped out
+ pw.print(memInfo.nativeSwappedOut); pw.print(',');
+ pw.print(memInfo.dalvikSwappedOut); pw.print(',');
+ pw.print(memInfo.otherSwappedOut); pw.print(',');
+ pw.print(memInfo.getTotalSwappedOut()); pw.print(',');
+
+ // Heap info - swapped out pss
+ if (memInfo.hasSwappedOutPss) {
+ pw.print(memInfo.nativeSwappedOutPss); pw.print(',');
+ pw.print(memInfo.dalvikSwappedOutPss); pw.print(',');
+ pw.print(memInfo.otherSwappedOutPss); pw.print(',');
+ pw.print(memInfo.getTotalSwappedOutPss()); pw.print(',');
+ } else {
+ pw.print("N/A,");
+ pw.print("N/A,");
+ pw.print("N/A,");
+ pw.print("N/A,");
+ }
+
+ // Heap info - other areas
+ for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
+ pw.print(Debug.MemoryInfo.getOtherLabel(i)); pw.print(',');
+ pw.print(memInfo.getOtherPss(i)); pw.print(',');
+ pw.print(memInfo.getOtherSwappablePss(i)); pw.print(',');
+ pw.print(memInfo.getOtherSharedDirty(i)); pw.print(',');
+ pw.print(memInfo.getOtherSharedClean(i)); pw.print(',');
+ pw.print(memInfo.getOtherPrivateDirty(i)); pw.print(',');
+ pw.print(memInfo.getOtherPrivateClean(i)); pw.print(',');
+ pw.print(memInfo.getOtherSwappedOut(i)); pw.print(',');
+ if (memInfo.hasSwappedOutPss) {
+ pw.print(memInfo.getOtherSwappedOutPss(i)); pw.print(',');
+ } else {
+ pw.print("N/A,");
+ }
+ }
+ return;
+ }
+
+ if (!dumpSummaryOnly) {
+ if (dumpFullInfo) {
+ printRow(pw, HEAP_FULL_COLUMN, "", "Pss", "Pss", "Shared", "Private",
+ "Shared", "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap",
+ "Rss", "Heap", "Heap", "Heap");
+ printRow(pw, HEAP_FULL_COLUMN, "", "Total", "Clean", "Dirty", "Dirty",
+ "Clean", "Clean", "Dirty", "Total",
+ "Size", "Alloc", "Free");
+ printRow(pw, HEAP_FULL_COLUMN, "", "------", "------", "------", "------",
+ "------", "------", "------", "------", "------", "------", "------");
+ printRow(pw, HEAP_FULL_COLUMN, "Native Heap", memInfo.nativePss,
+ memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
+ memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
+ memInfo.nativePrivateClean, memInfo.hasSwappedOutPss ?
+ memInfo.nativeSwappedOutPss : memInfo.nativeSwappedOut,
+ memInfo.nativeRss, nativeMax, nativeAllocated, nativeFree);
+ printRow(pw, HEAP_FULL_COLUMN, "Dalvik Heap", memInfo.dalvikPss,
+ memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
+ memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
+ memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss ?
+ memInfo.dalvikSwappedOutPss : memInfo.dalvikSwappedOut,
+ memInfo.dalvikRss, dalvikMax, dalvikAllocated, dalvikFree);
+ } else {
+ printRow(pw, HEAP_COLUMN, "", "Pss", "Private",
+ "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap",
+ "Rss", "Heap", "Heap", "Heap");
+ printRow(pw, HEAP_COLUMN, "", "Total", "Dirty",
+ "Clean", "Dirty", "Total", "Size", "Alloc", "Free");
+ printRow(pw, HEAP_COLUMN, "", "------", "------", "------",
+ "------", "------", "------", "------", "------", "------");
+ printRow(pw, HEAP_COLUMN, "Native Heap", memInfo.nativePss,
+ memInfo.nativePrivateDirty,
+ memInfo.nativePrivateClean,
+ memInfo.hasSwappedOutPss ? memInfo.nativeSwappedOutPss :
+ memInfo.nativeSwappedOut, memInfo.nativeRss,
+ nativeMax, nativeAllocated, nativeFree);
+ printRow(pw, HEAP_COLUMN, "Dalvik Heap", memInfo.dalvikPss,
+ memInfo.dalvikPrivateDirty,
+ memInfo.dalvikPrivateClean,
+ memInfo.hasSwappedOutPss ? memInfo.dalvikSwappedOutPss :
+ memInfo.dalvikSwappedOut, memInfo.dalvikRss,
+ dalvikMax, dalvikAllocated, dalvikFree);
+ }
+
+ int otherPss = memInfo.otherPss;
+ int otherSwappablePss = memInfo.otherSwappablePss;
+ int otherSharedDirty = memInfo.otherSharedDirty;
+ int otherPrivateDirty = memInfo.otherPrivateDirty;
+ int otherSharedClean = memInfo.otherSharedClean;
+ int otherPrivateClean = memInfo.otherPrivateClean;
+ int otherSwappedOut = memInfo.otherSwappedOut;
+ int otherSwappedOutPss = memInfo.otherSwappedOutPss;
+ int otherRss = memInfo.otherRss;
+
+ for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
+ final int myPss = memInfo.getOtherPss(i);
+ final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+ final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+ final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+ final int mySharedClean = memInfo.getOtherSharedClean(i);
+ final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+ final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+ final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+ final int myRss = memInfo.getOtherRss(i);
+ if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+ || mySharedClean != 0 || myPrivateClean != 0 || myRss != 0
+ || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+ if (dumpFullInfo) {
+ printRow(pw, HEAP_FULL_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+ myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+ mySharedClean, myPrivateClean,
+ memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+ myRss, "", "", "");
+ } else {
+ printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+ myPss, myPrivateDirty,
+ myPrivateClean,
+ memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+ myRss, "", "", "");
+ }
+ otherPss -= myPss;
+ otherSwappablePss -= mySwappablePss;
+ otherSharedDirty -= mySharedDirty;
+ otherPrivateDirty -= myPrivateDirty;
+ otherSharedClean -= mySharedClean;
+ otherPrivateClean -= myPrivateClean;
+ otherSwappedOut -= mySwappedOut;
+ otherSwappedOutPss -= mySwappedOutPss;
+ otherRss -= myRss;
+ }
+ }
+
+ if (dumpFullInfo) {
+ printRow(pw, HEAP_FULL_COLUMN, "Unknown", otherPss, otherSwappablePss,
+ otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
+ memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut,
+ otherRss, "", "", "");
+ printRow(pw, HEAP_FULL_COLUMN, "TOTAL", memInfo.getTotalPss(),
+ memInfo.getTotalSwappablePss(),
+ memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
+ memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
+ memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :
+ memInfo.getTotalSwappedOut(), memInfo.getTotalRss(),
+ nativeMax+dalvikMax, nativeAllocated+dalvikAllocated,
+ nativeFree+dalvikFree);
+ } else {
+ printRow(pw, HEAP_COLUMN, "Unknown", otherPss,
+ otherPrivateDirty, otherPrivateClean,
+ memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut,
+ otherRss, "", "", "");
+ printRow(pw, HEAP_COLUMN, "TOTAL", memInfo.getTotalPss(),
+ memInfo.getTotalPrivateDirty(),
+ memInfo.getTotalPrivateClean(),
+ memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :
+ memInfo.getTotalSwappedOut(), memInfo.getTotalRss(),
+ nativeMax+dalvikMax,
+ nativeAllocated+dalvikAllocated, nativeFree+dalvikFree);
+ }
+
+ if (dumpDalvik) {
+ pw.println(" ");
+ pw.println(" Dalvik Details");
+
+ for (int i=Debug.MemoryInfo.NUM_OTHER_STATS;
+ i<Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS; i++) {
+ final int myPss = memInfo.getOtherPss(i);
+ final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+ final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+ final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+ final int mySharedClean = memInfo.getOtherSharedClean(i);
+ final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+ final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+ final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+ final int myRss = memInfo.getOtherRss(i);
+ if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+ || mySharedClean != 0 || myPrivateClean != 0
+ || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+ if (dumpFullInfo) {
+ printRow(pw, HEAP_FULL_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+ myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+ mySharedClean, myPrivateClean,
+ memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+ myRss, "", "", "");
+ } else {
+ printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+ myPss, myPrivateDirty,
+ myPrivateClean,
+ memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+ myRss, "", "", "");
+ }
+ }
+ }
+ }
+ }
+
+ pw.println(" ");
+ pw.println(" App Summary");
+ printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "Pss(KB)", "", "Rss(KB)");
+ printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "------", "", "------");
+ printRow(pw, TWO_COUNT_COLUMNS,
+ "Java Heap:", memInfo.getSummaryJavaHeap(), "", memInfo.getSummaryJavaHeapRss());
+ printRow(pw, TWO_COUNT_COLUMNS,
+ "Native Heap:", memInfo.getSummaryNativeHeap(), "",
+ memInfo.getSummaryNativeHeapRss());
+ printRow(pw, TWO_COUNT_COLUMNS,
+ "Code:", memInfo.getSummaryCode(), "", memInfo.getSummaryCodeRss());
+ printRow(pw, TWO_COUNT_COLUMNS,
+ "Stack:", memInfo.getSummaryStack(), "", memInfo.getSummaryStackRss());
+ printRow(pw, TWO_COUNT_COLUMNS,
+ "Graphics:", memInfo.getSummaryGraphics(), "", memInfo.getSummaryGraphicsRss());
+ printRow(pw, ONE_COUNT_COLUMN,
+ "Private Other:", memInfo.getSummaryPrivateOther());
+ printRow(pw, ONE_COUNT_COLUMN,
+ "System:", memInfo.getSummarySystem());
+ printRow(pw, ONE_ALT_COUNT_COLUMN,
+ "Unknown:", "", "", memInfo.getSummaryUnknownRss());
+ pw.println(" ");
+ if (memInfo.hasSwappedOutPss) {
+ printRow(pw, THREE_COUNT_COLUMNS,
+ "TOTAL PSS:", memInfo.getSummaryTotalPss(),
+ "TOTAL RSS:", memInfo.getTotalRss(),
+ "TOTAL SWAP PSS:", memInfo.getSummaryTotalSwapPss());
+ } else {
+ printRow(pw, THREE_COUNT_COLUMNS,
+ "TOTAL PSS:", memInfo.getSummaryTotalPss(),
+ "TOTAL RSS:", memInfo.getTotalRss(),
+ "TOTAL SWAP (KB):", memInfo.getSummaryTotalSwap());
+ }
+ }
+
+ /**
+ * Dump heap info to proto.
+ *
+ * @param hasSwappedOutPss determines whether to use dirtySwap or dirtySwapPss
+ */
+ private static void dumpMemoryInfo(ProtoOutputStream proto, long fieldId, String name,
+ int pss, int cleanPss, int sharedDirty, int privateDirty,
+ int sharedClean, int privateClean,
+ boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss, int rss) {
+ final long token = proto.start(fieldId);
+
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.NAME, name);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
+ if (hasSwappedOutPss) {
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
+ } else {
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
+ }
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_RSS_KB, rss);
+
+ proto.end(token);
+ }
+
+ /**
+ * Dump mem info data to proto.
+ */
+ public static void dumpMemInfoTable(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
+ boolean dumpDalvik, boolean dumpSummaryOnly,
+ long nativeMax, long nativeAllocated, long nativeFree,
+ long dalvikMax, long dalvikAllocated, long dalvikFree) {
+
+ if (!dumpSummaryOnly) {
+ final long nhToken = proto.start(MemInfoDumpProto.ProcessMemory.NATIVE_HEAP);
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap",
+ memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
+ memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
+ memInfo.nativePrivateClean, memInfo.hasSwappedOutPss,
+ memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss,
+ memInfo.nativeRss);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree);
+ proto.end(nhToken);
+
+ final long dvToken = proto.start(MemInfoDumpProto.ProcessMemory.DALVIK_HEAP);
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
+ memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
+ memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
+ memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss,
+ memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss,
+ memInfo.dalvikRss);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree);
+ proto.end(dvToken);
+
+ int otherPss = memInfo.otherPss;
+ int otherSwappablePss = memInfo.otherSwappablePss;
+ int otherSharedDirty = memInfo.otherSharedDirty;
+ int otherPrivateDirty = memInfo.otherPrivateDirty;
+ int otherSharedClean = memInfo.otherSharedClean;
+ int otherPrivateClean = memInfo.otherPrivateClean;
+ int otherSwappedOut = memInfo.otherSwappedOut;
+ int otherSwappedOutPss = memInfo.otherSwappedOutPss;
+ int otherRss = memInfo.otherRss;
+
+ for (int i = 0; i < Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
+ final int myPss = memInfo.getOtherPss(i);
+ final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+ final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+ final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+ final int mySharedClean = memInfo.getOtherSharedClean(i);
+ final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+ final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+ final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+ final int myRss = memInfo.getOtherRss(i);
+ if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+ || mySharedClean != 0 || myPrivateClean != 0 || myRss != 0
+ || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.OTHER_HEAPS,
+ Debug.MemoryInfo.getOtherLabel(i),
+ myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+ mySharedClean, myPrivateClean,
+ memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss, myRss);
+
+ otherPss -= myPss;
+ otherSwappablePss -= mySwappablePss;
+ otherSharedDirty -= mySharedDirty;
+ otherPrivateDirty -= myPrivateDirty;
+ otherSharedClean -= mySharedClean;
+ otherPrivateClean -= myPrivateClean;
+ otherSwappedOut -= mySwappedOut;
+ otherSwappedOutPss -= mySwappedOutPss;
+ otherRss -= myRss;
+ }
+ }
+
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.UNKNOWN_HEAP, "Unknown",
+ otherPss, otherSwappablePss,
+ otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
+ memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss, otherRss);
+ final long tToken = proto.start(MemInfoDumpProto.ProcessMemory.TOTAL_HEAP);
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL",
+ memInfo.getTotalPss(), memInfo.getTotalSwappablePss(),
+ memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
+ memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
+ memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(),
+ memInfo.getTotalSwappedOutPss(), memInfo.getTotalRss());
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB,
+ nativeMax + dalvikMax);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
+ nativeAllocated + dalvikAllocated);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB,
+ nativeFree + dalvikFree);
+ proto.end(tToken);
+
+ if (dumpDalvik) {
+ for (int i = Debug.MemoryInfo.NUM_OTHER_STATS;
+ i < Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS;
+ i++) {
+ final int myPss = memInfo.getOtherPss(i);
+ final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+ final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+ final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+ final int mySharedClean = memInfo.getOtherSharedClean(i);
+ final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+ final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+ final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+ final int myRss = memInfo.getOtherRss(i);
+ if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+ || mySharedClean != 0 || myPrivateClean != 0
+ || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.DALVIK_DETAILS,
+ Debug.MemoryInfo.getOtherLabel(i),
+ myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+ mySharedClean, myPrivateClean,
+ memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss, myRss);
+ }
+ }
+ }
+ }
+
+ final long asToken = proto.start(MemInfoDumpProto.ProcessMemory.APP_SUMMARY);
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
+ memInfo.getSummaryJavaHeap());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB,
+ memInfo.getSummaryNativeHeap());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_PSS_KB,
+ memInfo.getSummaryCode());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_PSS_KB,
+ memInfo.getSummaryStack());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB,
+ memInfo.getSummaryGraphics());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
+ memInfo.getSummaryPrivateOther());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
+ memInfo.getSummarySystem());
+ if (memInfo.hasSwappedOutPss) {
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
+ memInfo.getSummaryTotalSwapPss());
+ } else {
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
+ memInfo.getSummaryTotalSwap());
+ }
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_RSS_KB,
+ memInfo.getSummaryJavaHeapRss());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.NATIVE_HEAP_RSS_KB,
+ memInfo.getSummaryNativeHeapRss());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_RSS_KB,
+ memInfo.getSummaryCodeRss());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_RSS_KB,
+ memInfo.getSummaryStackRss());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_RSS_KB,
+ memInfo.getSummaryGraphicsRss());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.UNKNOWN_RSS_KB,
+ memInfo.getSummaryUnknownRss());
+
+ proto.end(asToken);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void registerOnActivityPausedListener(Activity activity,
+ OnActivityPausedListener listener) {
+ synchronized (mOnPauseListeners) {
+ ArrayList<OnActivityPausedListener> list = mOnPauseListeners.get(activity);
+ if (list == null) {
+ list = new ArrayList<OnActivityPausedListener>();
+ mOnPauseListeners.put(activity, list);
+ }
+ list.add(listener);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void unregisterOnActivityPausedListener(Activity activity,
+ OnActivityPausedListener listener) {
+ synchronized (mOnPauseListeners) {
+ ArrayList<OnActivityPausedListener> list = mOnPauseListeners.get(activity);
+ if (list != null) {
+ list.remove(listener);
+ }
+ }
+ }
+
+ public final ActivityInfo resolveActivityInfo(Intent intent) {
+ ActivityInfo aInfo = intent.resolveActivityInfo(
+ mInitialApplication.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES);
+ if (aInfo == null) {
+ // Throw an exception.
+ Instrumentation.checkStartActivityResult(
+ ActivityManager.START_CLASS_NOT_FOUND, intent);
+ }
+ return aInfo;
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final Activity startActivityNow(Activity parent, String id,
+ Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
+ Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken,
+ IBinder shareableActivityToken) {
+ ActivityClientRecord r = new ActivityClientRecord();
+ r.token = token;
+ r.assistToken = assistToken;
+ r.shareableActivityToken = shareableActivityToken;
+ r.ident = 0;
+ r.intent = intent;
+ r.state = state;
+ r.parent = parent;
+ r.embeddedID = id;
+ r.activityInfo = activityInfo;
+ r.lastNonConfigurationInstances = lastNonConfigurationInstances;
+ if (localLOGV) {
+ ComponentName compname = intent.getComponent();
+ String name;
+ if (compname != null) {
+ name = compname.toShortString();
+ } else {
+ name = "(Intent " + intent + ").getComponent() returned null";
+ }
+ Slog.v(TAG, "Performing launch: action=" + intent.getAction()
+ + ", comp=" + name
+ + ", token=" + token);
+ }
+ // TODO(lifecycler): Can't switch to use #handleLaunchActivity() because it will try to
+ // call #reportSizeConfigurations(), but the server might not know anything about the
+ // activity if it was launched from LocalAcvitivyManager.
+ return performLaunchActivity(r, null /* customIntent */);
+ }
+
+ @UnsupportedAppUsage
+ public final Activity getActivity(IBinder token) {
+ final ActivityClientRecord activityRecord = mActivities.get(token);
+ return activityRecord != null ? activityRecord.activity : null;
+ }
+
+ @Override
+ public ActivityClientRecord getActivityClient(IBinder token) {
+ return mActivities.get(token);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public Configuration getConfiguration() {
+ return mConfigurationController.getConfiguration();
+ }
+
+ @Override
+ public void updatePendingConfiguration(Configuration config) {
+ final Configuration updatedConfig =
+ mConfigurationController.updatePendingConfiguration(config);
+ // This is only done to maintain @UnsupportedAppUsage and should be removed someday.
+ if (updatedConfig != null) {
+ mPendingConfiguration = updatedConfig;
+ }
+ }
+
+ @Override
+ public void updateProcessState(int processState, boolean fromIpc) {
+ synchronized (mAppThread) {
+ if (mLastProcessState == processState) {
+ return;
+ }
+ // Do not issue a transitional GC if we are transitioning between 2 cached states.
+ // Only update if the state flips between cached and uncached or vice versa
+ if (ActivityManager.isProcStateCached(mLastProcessState)
+ != ActivityManager.isProcStateCached(processState)) {
+ updateVmProcessState(processState);
+ }
+ mLastProcessState = processState;
+ if (localLOGV) {
+ Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
+ + (fromIpc ? " (from ipc" : ""));
+ }
+ }
+ }
+
+ /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
+ // Currently ART VM only uses state updates for Transitional GC, and thus
+ // this function initiates a Transitional GC for transitions into Cached apps states.
+ private void updateVmProcessState(int processState) {
+ // Only a transition into Cached state should result in a Transitional GC request
+ // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
+ // Note that there are 4 possible cached states currently, all of which are
+ // JANK_IMPERCEPTIBLE from GC point of view.
+ final int state = ActivityManager.isProcStateCached(processState)
+ ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
+ : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
+ VMRuntime.getRuntime().updateProcessState(state);
+ }
+
+ @Override
+ public void countLaunchingActivities(int num) {
+ mNumLaunchingActivities.getAndAdd(num);
+ }
+
+ @UnsupportedAppUsage
+ public final void sendActivityResult(
+ IBinder token, String id, int requestCode,
+ int resultCode, Intent data) {
+ if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
+ + " req=" + requestCode + " res=" + resultCode + " data=" + data);
+ ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
+ list.add(new ResultInfo(id, requestCode, resultCode, data));
+ final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
+ clientTransaction.addCallback(ActivityResultItem.obtain(list));
+ try {
+ mAppThread.scheduleTransaction(clientTransaction);
+ } catch (RemoteException e) {
+ // Local scheduling
+ }
+ }
+
+ @Override
+ TransactionExecutor getTransactionExecutor() {
+ return mTransactionExecutor;
+ }
+
+ void sendMessage(int what, Object obj) {
+ sendMessage(what, obj, 0, 0, false);
+ }
+
+ private void sendMessage(int what, Object obj, int arg1) {
+ sendMessage(what, obj, arg1, 0, false);
+ }
+
+ private void sendMessage(int what, Object obj, int arg1, int arg2) {
+ sendMessage(what, obj, arg1, arg2, false);
+ }
+
+ private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
+ if (DEBUG_MESSAGES) {
+ Slog.v(TAG,
+ "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
+ }
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.obj = obj;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ if (async) {
+ msg.setAsynchronous(true);
+ }
+ mH.sendMessage(msg);
+ }
+
+ final void scheduleContextCleanup(ContextImpl context, String who,
+ String what) {
+ ContextCleanupInfo cci = new ContextCleanupInfo();
+ cci.context = context;
+ cci.who = who;
+ cci.what = what;
+ sendMessage(H.CLEAN_UP_CONTEXT, cci);
+ }
+
+ /** Core implementation of activity launch. */
+ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
+ ActivityInfo aInfo = r.activityInfo;
+ if (r.packageInfo == null) {
+ r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
+ Context.CONTEXT_INCLUDE_CODE);
+ }
+
+ ComponentName component = r.intent.getComponent();
+ if (component == null) {
+ component = r.intent.resolveActivity(
+ mInitialApplication.getPackageManager());
+ r.intent.setComponent(component);
+ }
+
+ if (r.activityInfo.targetActivity != null) {
+ component = new ComponentName(r.activityInfo.packageName,
+ r.activityInfo.targetActivity);
+ }
+
+ ContextImpl appContext = createBaseContextForActivity(r);
+ Activity activity = null;
+ try {
+ java.lang.ClassLoader cl = appContext.getClassLoader();
+ activity = mInstrumentation.newActivity(
+ cl, component.getClassName(), r.intent);
+ StrictMode.incrementExpectedActivityCount(activity.getClass());
+ r.intent.setExtrasClassLoader(cl);
+ r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
+ appContext.getAttributionSource());
+ if (r.state != null) {
+ r.state.setClassLoader(cl);
+ }
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(activity, e)) {
+ throw new RuntimeException(
+ "Unable to instantiate activity " + component
+ + ": " + e.toString(), e);
+ }
+ }
+
+ try {
+ Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
+
+ if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
+ if (localLOGV) Slog.v(
+ TAG, r + ": app=" + app
+ + ", appName=" + app.getPackageName()
+ + ", pkg=" + r.packageInfo.getPackageName()
+ + ", comp=" + r.intent.getComponent().toShortString()
+ + ", dir=" + r.packageInfo.getAppDir());
+
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.put(r.token, r);
+ }
+
+ if (activity != null) {
+ CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
+ Configuration config =
+ new Configuration(mConfigurationController.getCompatConfiguration());
+ if (r.overrideConfig != null) {
+ config.updateFrom(r.overrideConfig);
+ }
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ + r.activityInfo.name + " with config " + config);
+ Window window = null;
+ if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
+ window = r.mPendingRemoveWindow;
+ r.mPendingRemoveWindow = null;
+ r.mPendingRemoveWindowManager = null;
+ }
+
+ // Activity resources must be initialized with the same loaders as the
+ // application context.
+ appContext.getResources().addLoaders(
+ app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
+
+ appContext.setOuterContext(activity);
+ activity.attach(appContext, this, getInstrumentation(), r.token,
+ r.ident, app, r.intent, r.activityInfo, title, r.parent,
+ r.embeddedID, r.lastNonConfigurationInstances, config,
+ r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
+ r.assistToken, r.shareableActivityToken);
+
+ if (customIntent != null) {
+ activity.mIntent = customIntent;
+ }
+ r.lastNonConfigurationInstances = null;
+ checkAndBlockForNetworkAccess();
+ activity.mStartedActivity = false;
+ int theme = r.activityInfo.getThemeResource();
+ if (theme != 0) {
+ activity.setTheme(theme);
+ }
+
+ if (r.mActivityOptions != null) {
+ activity.mPendingOptions = r.mActivityOptions;
+ r.mActivityOptions = null;
+ }
+ activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
+ activity.mCalled = false;
+ // Assigning the activity to the record before calling onCreate() allows
+ // ActivityThread#getActivity() lookup for the callbacks triggered from
+ // ActivityLifecycleCallbacks#onActivityCreated() or
+ // ActivityLifecycleCallback#onActivityPostCreated().
+ r.activity = activity;
+ if (r.isPersistable()) {
+ mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
+ } else {
+ mInstrumentation.callActivityOnCreate(activity, r.state);
+ }
+ if (!activity.mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + r.intent.getComponent().toShortString() +
+ " did not call through to super.onCreate()");
+ }
+ r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
+ }
+ r.setState(ON_CREATE);
+
+ } catch (SuperNotCalledException e) {
+ throw e;
+
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(activity, e)) {
+ throw new RuntimeException(
+ "Unable to start activity " + component
+ + ": " + e.toString(), e);
+ }
+ }
+
+ return activity;
+ }
+
+ @Override
+ public void handleStartActivity(ActivityClientRecord r,
+ PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
+ final Activity activity = r.activity;
+ if (!r.stopped) {
+ throw new IllegalStateException("Can't start activity that is not stopped.");
+ }
+ if (r.activity.mFinished) {
+ // TODO(lifecycler): How can this happen?
+ return;
+ }
+
+ unscheduleGcIdler();
+ if (activityOptions != null) {
+ activity.mPendingOptions = activityOptions;
+ }
+
+ // Start
+ activity.performStart("handleStartActivity");
+ r.setState(ON_START);
+
+ if (pendingActions == null) {
+ // No more work to do.
+ return;
+ }
+
+ // Restore instance state
+ if (pendingActions.shouldRestoreInstanceState()) {
+ if (r.isPersistable()) {
+ if (r.state != null || r.persistentState != null) {
+ mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
+ r.persistentState);
+ }
+ } else if (r.state != null) {
+ mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
+ }
+ }
+
+ // Call postOnCreate()
+ if (pendingActions.shouldCallOnPostCreate()) {
+ activity.mCalled = false;
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onPostCreate");
+ if (r.isPersistable()) {
+ mInstrumentation.callActivityOnPostCreate(activity, r.state,
+ r.persistentState);
+ } else {
+ mInstrumentation.callActivityOnPostCreate(activity, r.state);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ if (!activity.mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + r.intent.getComponent().toShortString()
+ + " did not call through to super.onPostCreate()");
+ }
+ }
+
+ updateVisibility(r, true /* show */);
+ mSomeActivitiesChanged = true;
+ }
+
+ /**
+ * Checks if {@link #mNetworkBlockSeq} is {@link #INVALID_PROC_STATE_SEQ} and if so, returns
+ * immediately. Otherwise, makes a blocking call to ActivityManagerService to wait for the
+ * network rules to get updated.
+ */
+ private void checkAndBlockForNetworkAccess() {
+ synchronized (mNetworkPolicyLock) {
+ if (mNetworkBlockSeq != INVALID_PROC_STATE_SEQ) {
+ try {
+ ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
+ mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+ } catch (RemoteException ignored) {}
+ }
+ }
+ }
+
+ private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
+ final int displayId = ActivityClient.getInstance().getDisplayId(r.token);
+ ContextImpl appContext = ContextImpl.createActivityContext(
+ this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
+
+ final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ // For debugging purposes, if the activity's package name contains the value of
+ // the "debug.use-second-display" system property as a substring, then show
+ // its content on a secondary display if there is one.
+ String pkgName = SystemProperties.get("debug.second-display.pkg");
+ if (pkgName != null && !pkgName.isEmpty()
+ && r.packageInfo.mPackageName.contains(pkgName)) {
+ for (int id : dm.getDisplayIds()) {
+ if (id != DEFAULT_DISPLAY) {
+ Display display =
+ dm.getCompatibleDisplay(id, appContext.getResources());
+ appContext = (ContextImpl) appContext.createDisplayContext(display);
+ break;
+ }
+ }
+ }
+ return appContext;
+ }
+
+ /**
+ * Extended implementation of activity launch. Used when server requests a launch or relaunch.
+ */
+ @Override
+ public Activity handleLaunchActivity(ActivityClientRecord r,
+ PendingTransactionActions pendingActions, int deviceId, Intent customIntent) {
+ // If we are getting ready to gc after going to the background, well
+ // we are back active so skip it.
+ unscheduleGcIdler();
+ mSomeActivitiesChanged = true;
+
+ if (r.profilerInfo != null) {
+ mProfiler.setProfiler(r.profilerInfo);
+ mProfiler.startProfiling();
+ }
+
+ // Make sure we are running with the most recent config.
+ mConfigurationController.handleConfigurationChanged(null, null);
+ updateDeviceIdForNonUIContexts(deviceId);
+
+ if (localLOGV) Slog.v(
+ TAG, "Handling launch of " + r);
+
+ // Initialize before creating the activity
+ if (ThreadedRenderer.sRendererEnabled
+ && (r.activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ HardwareRenderer.preload();
+ }
+ WindowManagerGlobal.initialize();
+
+ // Hint the GraphicsEnvironment that an activity is launching on the process.
+ GraphicsEnvironment.hintActivityLaunch();
+
+ final Activity a = performLaunchActivity(r, customIntent);
+
+ if (a != null) {
+ r.createdConfig = new Configuration(mConfigurationController.getConfiguration());
+ reportSizeConfigurations(r);
+ if (!r.activity.mFinished && pendingActions != null) {
+ pendingActions.setOldState(r.state);
+ pendingActions.setRestoreInstanceState(true);
+ pendingActions.setCallOnPostCreate(true);
+ }
+ } else {
+ // If there was an error, for any reason, tell the activity manager to stop us.
+ ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
+ null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
+ }
+
+ return a;
+ }
+
+ private void reportSizeConfigurations(ActivityClientRecord r) {
+ if (mActivitiesToBeDestroyed.containsKey(r.token)) {
+ // Size configurations of a destroyed activity is meaningless.
+ return;
+ }
+ Configuration[] configurations = r.activity.getResources().getSizeConfigurations();
+ if (configurations == null) {
+ return;
+ }
+ r.mSizeConfigurations = new SizeConfigurationBuckets(configurations);
+ ActivityClient.getInstance().reportSizeConfigurations(r.token, r.mSizeConfigurations);
+ }
+
+ private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
+ final int N = intents.size();
+ for (int i=0; i<N; i++) {
+ ReferrerIntent intent = intents.get(i);
+ intent.setExtrasClassLoader(r.activity.getClassLoader());
+ intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
+ r.activity.getAttributionSource());
+ r.activity.mFragments.noteStateNotSaved();
+ mInstrumentation.callActivityOnNewIntent(r.activity, intent);
+ }
+ }
+
+ @Override
+ public void handleNewIntent(ActivityClientRecord r, List<ReferrerIntent> intents) {
+ checkAndBlockForNetworkAccess();
+ deliverNewIntents(r, intents);
+ }
+
+ public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
+ // Filling for autofill has a few differences:
+ // - it does not need an AssistContent
+ // - it does not call onProvideAssistData()
+ // - it needs an IAutoFillCallback
+ boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL;
+ // When only the AssistContent is requested, omit the AsssistStructure
+ boolean requestedOnlyContent = cmd.requestType == ActivityManager.ASSIST_CONTEXT_CONTENT;
+
+ // TODO: decide if lastSessionId logic applies to autofill sessions
+ if (mLastSessionId != cmd.sessionId) {
+ // Clear the existing structures
+ mLastSessionId = cmd.sessionId;
+ for (int i = mLastAssistStructures.size() - 1; i >= 0; i--) {
+ AssistStructure structure = mLastAssistStructures.get(i).get();
+ if (structure != null) {
+ structure.clearSendChannel();
+ }
+ mLastAssistStructures.remove(i);
+ }
+ }
+
+ Bundle data = new Bundle();
+ AssistStructure structure = null;
+ AssistContent content = forAutofill ? null : new AssistContent();
+ final long startTime = SystemClock.uptimeMillis();
+ ActivityClientRecord r = mActivities.get(cmd.activityToken);
+ Uri referrer = null;
+ if (r != null) {
+ if (!forAutofill) {
+ r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
+ r.activity.onProvideAssistData(data);
+ referrer = r.activity.onProvideReferrer();
+ }
+ if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill
+ || requestedOnlyContent) {
+ if (!requestedOnlyContent) {
+ structure = new AssistStructure(r.activity, forAutofill, cmd.flags);
+ }
+ Intent activityIntent = r.activity.getIntent();
+ boolean notSecure = r.window == null ||
+ (r.window.getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SECURE) == 0;
+ if (activityIntent != null && notSecure) {
+ if (!forAutofill) {
+ Intent intent = new Intent(activityIntent);
+ intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+ content.setDefaultIntent(intent);
+ }
+ } else {
+ if (!forAutofill) {
+ content.setDefaultIntent(new Intent());
+ }
+ }
+ if (!forAutofill) {
+ r.activity.onProvideAssistContent(content);
+ }
+ }
+ }
+
+ if (!requestedOnlyContent) {
+ if (structure == null) {
+ structure = new AssistStructure();
+ }
+
+ // TODO: decide if lastSessionId logic applies to autofill sessions
+
+ structure.setAcquisitionStartTime(startTime);
+ structure.setAcquisitionEndTime(SystemClock.uptimeMillis());
+
+ mLastAssistStructures.add(new WeakReference<>(structure));
+ }
+
+ IActivityTaskManager mgr = ActivityTaskManager.getService();
+ try {
+ mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Fetches the user actions for the corresponding activity */
+ private void handleRequestDirectActions(@NonNull IBinder activityToken,
+ @NonNull IVoiceInteractor interactor, @NonNull CancellationSignal cancellationSignal,
+ @NonNull RemoteCallback callback, int retryCount) {
+ final ActivityClientRecord r = mActivities.get(activityToken);
+ if (r == null) {
+ Log.w(TAG, "requestDirectActions(): no activity for " + activityToken);
+ callback.sendResult(null);
+ return;
+ }
+ final int lifecycleState = r.getLifecycleState();
+ if (lifecycleState < ON_START) {
+ // TODO(b/234173463): requestDirectActions callback should indicate errors
+ if (retryCount > 0) {
+ mH.sendMessageDelayed(
+ PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
+ ActivityThread.this, activityToken, interactor, cancellationSignal,
+ callback, retryCount - 1), REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS);
+ return;
+ }
+ Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
+ callback.sendResult(null);
+ return;
+ }
+ if (lifecycleState >= ON_STOP) {
+ Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
+ callback.sendResult(null);
+ return;
+ }
+ if (r.activity.mVoiceInteractor == null
+ || r.activity.mVoiceInteractor.mInteractor.asBinder()
+ != interactor.asBinder()) {
+ if (r.activity.mVoiceInteractor != null) {
+ r.activity.mVoiceInteractor.destroy();
+ }
+ r.activity.mVoiceInteractor = new VoiceInteractor(interactor, r.activity,
+ r.activity, Looper.myLooper());
+ }
+ r.activity.onGetDirectActions(cancellationSignal, (actions) -> {
+ Objects.requireNonNull(actions);
+ Preconditions.checkCollectionElementsNotNull(actions, "actions");
+ if (!actions.isEmpty()) {
+ final int actionCount = actions.size();
+ for (int i = 0; i < actionCount; i++) {
+ final DirectAction action = actions.get(i);
+ action.setSource(r.activity.getTaskId(), r.activity.getAssistToken());
+ }
+ final Bundle result = new Bundle();
+ result.putParcelable(DirectAction.KEY_ACTIONS_LIST,
+ new ParceledListSlice<>(actions));
+ callback.sendResult(result);
+ } else {
+ callback.sendResult(null);
+ }
+ });
+ }
+
+ /** Performs an actions in the corresponding activity */
+ private void handlePerformDirectAction(@NonNull IBinder activityToken,
+ @NonNull String actionId, @Nullable Bundle arguments,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull RemoteCallback resultCallback) {
+ final ActivityClientRecord r = mActivities.get(activityToken);
+ if (r != null) {
+ final int lifecycleState = r.getLifecycleState();
+ if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
+ resultCallback.sendResult(null);
+ return;
+ }
+ final Bundle nonNullArguments = (arguments != null) ? arguments : Bundle.EMPTY;
+ r.activity.onPerformDirectAction(actionId, nonNullArguments, cancellationSignal,
+ resultCallback::sendResult);
+ } else {
+ resultCallback.sendResult(null);
+ }
+ }
+
+ public void handleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
+ ActivityClientRecord r = mActivities.get(token);
+ if (r != null) {
+ r.activity.onTranslucentConversionComplete(drawComplete);
+ }
+ }
+
+ public void onNewActivityOptions(IBinder token, ActivityOptions options) {
+ ActivityClientRecord r = mActivities.get(token);
+ if (r != null) {
+ r.activity.onNewActivityOptions(options);
+ }
+ }
+
+ public void handleInstallProvider(ProviderInfo info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ installContentProviders(mInitialApplication, Arrays.asList(info));
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ private void handleEnterAnimationComplete(IBinder token) {
+ ActivityClientRecord r = mActivities.get(token);
+ if (r != null) {
+ r.activity.dispatchEnterAnimationComplete();
+ }
+ }
+
+ private void handleStartBinderTracking() {
+ Binder.enableStackTracking();
+ }
+
+ private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) {
+ try {
+ Binder.disableStackTracking();
+ Binder.getTransactionTracker().writeTracesToFile(fd);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ Binder.getTransactionTracker().clearTraces();
+ }
+ }
+
+ @Override
+ public void handlePictureInPictureRequested(ActivityClientRecord r) {
+ final boolean receivedByApp = r.activity.onPictureInPictureRequested();
+ if (!receivedByApp) {
+ // Previous recommendation was for apps to enter picture-in-picture in
+ // onUserLeavingHint() for cases such as the app being put into the background. For
+ // backwards compatibility with apps that are not using the newer
+ // onPictureInPictureRequested() callback, we schedule the life cycle events needed to
+ // trigger onUserLeavingHint(), then we return the activity to its previous state.
+ schedulePauseWithUserLeaveHintAndReturnToCurrentState(r);
+ }
+ }
+
+ @Override
+ public void handlePictureInPictureStateChanged(@NonNull ActivityClientRecord r,
+ PictureInPictureUiState pipState) {
+ r.activity.onPictureInPictureUiStateChanged(pipState);
+ }
+
+ /**
+ * Register a splash screen manager to this process.
+ */
+ public void registerSplashScreenManager(
+ @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
+ synchronized (this) {
+ mSplashScreenGlobal = manager;
+ }
+ }
+
+ @Override
+ public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
+ synchronized (this) {
+ return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
+ }
+ }
+
+ @Override
+ public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+ @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
+ final DecorView decorView = (DecorView) r.window.peekDecorView();
+ if (parcelable != null && decorView != null) {
+ createSplashScreen(r, decorView, parcelable, startingWindowLeash);
+ } else {
+ // shouldn't happen!
+ Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
+ }
+ }
+
+ private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
+ SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
+ final SplashScreenView view = builder.createFromParcel(parcelable).build();
+ view.attachHostWindow(r.window);
+ decorView.addView(view);
+ view.requestLayout();
+
+ view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ private boolean mHandled = false;
+ @Override
+ public void onDraw() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+ // Transfer the splash screen view from shell to client.
+ // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure
+ // the client view is ready to show and we can use applyTransactionOnDraw to make
+ // all transitions happen at the same frame.
+ syncTransferSplashscreenViewTransaction(
+ view, r.token, decorView, startingWindowLeash);
+ view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
+ }
+ });
+ }
+
+ private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
+ ActivityClient.getInstance().reportSplashScreenAttached(token);
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.handOverSplashScreenView(token, view);
+ }
+ }
+ }
+
+ private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
+ View decorView, @NonNull SurfaceControl startingWindowLeash) {
+ // Ensure splash screen view is shown before remove the splash screen window.
+ // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
+ // to ensure the transfer of surface view and hide starting window are happen at the same
+ // frame.
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.hide(startingWindowLeash);
+
+ decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
+ view.syncTransferSurfaceOnDraw();
+ // Tell server we can remove the starting window
+ decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
+ }
+
+ /**
+ * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
+ * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
+ * onPictureInPictureRequested to enter picture-in-picture.
+ */
+ private void schedulePauseWithUserLeaveHintAndReturnToCurrentState(ActivityClientRecord r) {
+ final int prevState = r.getLifecycleState();
+ if (prevState != ON_RESUME && prevState != ON_PAUSE) {
+ return;
+ }
+
+ switch (prevState) {
+ case ON_RESUME:
+ // Schedule a PAUSE then return to RESUME.
+ schedulePauseWithUserLeavingHint(r);
+ scheduleResume(r);
+ break;
+ case ON_PAUSE:
+ // Schedule a RESUME then return to PAUSE.
+ scheduleResume(r);
+ schedulePauseWithUserLeavingHint(r);
+ break;
+ }
+ }
+
+ private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
+ final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+ transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.activity.isFinishing(),
+ /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false,
+ /* autoEnteringPip */ false));
+ executeTransaction(transaction);
+ }
+
+ private void scheduleResume(ActivityClientRecord r) {
+ final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+ transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false,
+ /* shouldSendCompatFakeFocus */ false));
+ executeTransaction(transaction);
+ }
+
+ private void handleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor interactor) {
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r != null) {
+ r.voiceInteractor = interactor;
+ r.activity.setVoiceInteractor(interactor);
+ if (interactor == null) {
+ r.activity.onLocalVoiceInteractionStopped();
+ } else {
+ r.activity.onLocalVoiceInteractionStarted();
+ }
+ }
+ }
+
+ private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) {
+ try {
+ VMDebug.attachAgent(agent, classLoader);
+ return true;
+ } catch (IOException e) {
+ Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent);
+ return false;
+ }
+ }
+
+ static void handleAttachAgent(String agent, LoadedApk loadedApk) {
+ ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null;
+ if (attemptAttachAgent(agent, classLoader)) {
+ return;
+ }
+ if (classLoader != null) {
+ attemptAttachAgent(agent, null);
+ }
+ }
+
+ static void handleAttachStartupAgents(String dataDir) {
+ try {
+ Path codeCache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
+ if (!Files.exists(codeCache)) {
+ return;
+ }
+ Path startupPath = codeCache.resolve("startup_agents");
+ if (Files.exists(startupPath)) {
+ try (DirectoryStream<Path> startupFiles = Files.newDirectoryStream(startupPath)) {
+ for (Path p : startupFiles) {
+ handleAttachAgent(
+ p.toAbsolutePath().toString()
+ + "="
+ + dataDir,
+ null);
+ }
+ }
+ }
+ } catch (Exception e) {
+ // Ignored.
+ }
+ }
+
+ private void updateUiTranslationState(IBinder activityToken, int state,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
+ UiTranslationSpec uiTranslationSpec) {
+ final ActivityClientRecord r = mActivities.get(activityToken);
+ if (r == null) {
+ Log.w(TAG, "updateUiTranslationState(): no activity for " + activityToken);
+ return;
+ }
+ r.activity.updateUiTranslationState(
+ state, sourceSpec, targetSpec, viewIds, uiTranslationSpec);
+ }
+
+ private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
+
+ /**
+ * Return the Intent that's currently being handled by a
+ * BroadcastReceiver on this thread, or null if none.
+ * @hide
+ */
+ public static Intent getIntentBeingBroadcast() {
+ return sCurrentBroadcastIntent.get();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private void handleReceiver(ReceiverData data) {
+ // If we are getting ready to gc after going to the background, well
+ // we are back active so skip it.
+ unscheduleGcIdler();
+
+ String component = data.intent.getComponent().getClassName();
+
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
+
+ IActivityManager mgr = ActivityManager.getService();
+
+ Application app;
+ BroadcastReceiver receiver;
+ ContextImpl context;
+ try {
+ app = packageInfo.makeApplicationInner(false, mInstrumentation);
+ context = (ContextImpl) app.getBaseContext();
+ if (data.info.splitName != null) {
+ context = (ContextImpl) context.createContextForSplit(data.info.splitName);
+ }
+ if (data.info.attributionTags != null && data.info.attributionTags.length > 0) {
+ final String attributionTag = data.info.attributionTags[0];
+ context = (ContextImpl) context.createAttributionContext(attributionTag);
+ }
+ java.lang.ClassLoader cl = context.getClassLoader();
+ data.intent.setExtrasClassLoader(cl);
+ data.intent.prepareToEnterProcess(
+ isProtectedComponent(data.info) || isProtectedBroadcast(data.intent),
+ context.getAttributionSource());
+ data.setExtrasClassLoader(cl);
+ receiver = packageInfo.getAppFactory()
+ .instantiateReceiver(cl, data.info.name, data.intent);
+ } catch (Exception e) {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing failed broadcast to " + data.intent.getComponent());
+ data.sendFinished(mgr);
+ throw new RuntimeException(
+ "Unable to instantiate receiver " + component
+ + ": " + e.toString(), e);
+ }
+
+ try {
+ if (localLOGV) Slog.v(
+ TAG, "Performing receive of " + data.intent
+ + ": app=" + app
+ + ", appName=" + app.getPackageName()
+ + ", pkg=" + packageInfo.getPackageName()
+ + ", comp=" + data.intent.getComponent().toShortString()
+ + ", dir=" + packageInfo.getAppDir());
+
+ sCurrentBroadcastIntent.set(data.intent);
+ receiver.setPendingResult(data);
+ receiver.onReceive(context.getReceiverRestrictedContext(),
+ data.intent);
+ } catch (Exception e) {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing failed broadcast to " + data.intent.getComponent());
+ data.sendFinished(mgr);
+ if (!mInstrumentation.onException(receiver, e)) {
+ throw new RuntimeException(
+ "Unable to start receiver " + component
+ + ": " + e.toString(), e);
+ }
+ } finally {
+ sCurrentBroadcastIntent.set(null);
+ }
+
+ if (receiver.getPendingResult() != null) {
+ data.finish();
+ }
+ }
+
+ // Instantiate a BackupAgent and tell it that it's alive
+ private void handleCreateBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data);
+
+ // Validity check the requested target package's uid against ours
+ try {
+ PackageInfo requestedPackage = getPackageManager().getPackageInfo(
+ data.appInfo.packageName, 0, UserHandle.myUserId());
+ if (requestedPackage.applicationInfo.uid != Process.myUid()) {
+ Slog.w(TAG, "Asked to instantiate non-matching package "
+ + data.appInfo.packageName);
+ return;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // no longer idle; we have backup work to do
+ unscheduleGcIdler();
+
+ // instantiate the BackupAgent class named in the manifest
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ if (packageName == null) {
+ Slog.d(TAG, "Asked to create backup agent for nonexistent package");
+ return;
+ }
+
+ String classname = getBackupAgentName(data);
+
+ try {
+ IBinder binder = null;
+ ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
+ BackupAgent agent = backupAgents.get(packageName);
+ if (agent != null) {
+ // reusing the existing instance
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "Reusing existing agent instance");
+ }
+ binder = agent.onBind();
+ } else {
+ try {
+ if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);
+
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ agent = (BackupAgent) cl.loadClass(classname).newInstance();
+
+ // set up the agent's context
+ ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
+ context.setOuterContext(agent);
+ agent.attach(context);
+
+ agent.onCreate(UserHandle.of(data.userId), data.backupDestination,
+ getOperationTypeFromBackupMode(data.backupMode));
+ binder = agent.onBind();
+ backupAgents.put(packageName, agent);
+ } catch (Exception e) {
+ // If this is during restore, fail silently; otherwise go
+ // ahead and let the user see the crash.
+ Slog.e(TAG, "Agent threw during creation: " + e);
+ if (data.backupMode != ApplicationThreadConstants.BACKUP_MODE_RESTORE
+ && data.backupMode !=
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL) {
+ throw e;
+ }
+ // falling through with 'binder' still null
+ }
+ }
+
+ // tell the OS that we're live now
+ try {
+ ActivityManager.getService().backupAgentCreated(packageName, binder, data.userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to create BackupAgent "
+ + classname + ": " + e.toString(), e);
+ }
+ }
+
+ @OperationType
+ private static int getOperationTypeFromBackupMode(int backupMode) {
+ switch (backupMode) {
+ case ApplicationThreadConstants.BACKUP_MODE_RESTORE:
+ case ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL:
+ return OperationType.RESTORE;
+ case ApplicationThreadConstants.BACKUP_MODE_FULL:
+ case ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL:
+ return OperationType.BACKUP;
+ default:
+ Slog.w(TAG, "Invalid backup mode when initialising BackupAgent: "
+ + backupMode);
+ return OperationType.UNKNOWN;
+ }
+ }
+
+ private String getBackupAgentName(CreateBackupAgentData data) {
+ String agentName = data.appInfo.backupAgentName;
+ // full backup operation but no app-supplied agent? use the default implementation
+ if (agentName == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) {
+ agentName = DEFAULT_FULL_BACKUP_AGENT;
+ }
+ return agentName;
+ }
+
+ // Tear down a BackupAgent
+ private void handleDestroyBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
+
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
+ BackupAgent agent = backupAgents.get(packageName);
+ if (agent != null) {
+ try {
+ agent.onDestroy();
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+ e.printStackTrace();
+ }
+ backupAgents.remove(packageName);
+ } else {
+ Slog.w(TAG, "Attempt to destroy unknown backup agent " + data);
+ }
+ }
+
+ private ArrayMap<String, BackupAgent> getBackupAgentsForUser(int userId) {
+ ArrayMap<String, BackupAgent> backupAgents = mBackupAgentsByUser.get(userId);
+ if (backupAgents == null) {
+ backupAgents = new ArrayMap<>();
+ mBackupAgentsByUser.put(userId, backupAgents);
+ }
+ return backupAgents;
+ }
+
+ @UnsupportedAppUsage
+ private void handleCreateService(CreateServiceData data) {
+ // If we are getting ready to gc after going to the background, well
+ // we are back active so skip it.
+ unscheduleGcIdler();
+
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
+ Service service = null;
+ try {
+ if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
+
+ Application app = packageInfo.makeApplicationInner(false, mInstrumentation);
+
+ final java.lang.ClassLoader cl;
+ if (data.info.splitName != null) {
+ cl = packageInfo.getSplitClassLoader(data.info.splitName);
+ } else {
+ cl = packageInfo.getClassLoader();
+ }
+ service = packageInfo.getAppFactory()
+ .instantiateService(cl, data.info.name, data.intent);
+ ContextImpl context = ContextImpl.getImpl(service
+ .createServiceBaseContext(this, packageInfo));
+ if (data.info.splitName != null) {
+ context = (ContextImpl) context.createContextForSplit(data.info.splitName);
+ }
+ if (data.info.attributionTags != null && data.info.attributionTags.length > 0) {
+ final String attributionTag = data.info.attributionTags[0];
+ context = (ContextImpl) context.createAttributionContext(attributionTag);
+ }
+ // Service resources must be initialized with the same loaders as the application
+ // context.
+ context.getResources().addLoaders(
+ app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
+
+ context.setOuterContext(service);
+ service.attach(context, this, data.info.name, data.token, app,
+ ActivityManager.getService());
+ if (!service.isUiContext()) { // WindowProviderService is a UI Context.
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
+ || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ service.updateDeviceId(mLastReportedDeviceId);
+ }
+ }
+ service.onCreate();
+ mServicesData.put(data.token, data);
+ mServices.put(data.token, service);
+ try {
+ ActivityManager.getService().serviceDoneExecuting(
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(service, e)) {
+ throw new RuntimeException(
+ "Unable to create service " + data.info.name
+ + ": " + e.toString(), e);
+ }
+ }
+ }
+
+ private void handleBindService(BindServiceData data) {
+ CreateServiceData createData = mServicesData.get(data.token);
+ Service s = mServices.get(data.token);
+ if (DEBUG_SERVICE)
+ Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
+ if (s != null) {
+ try {
+ data.intent.setExtrasClassLoader(s.getClassLoader());
+ data.intent.prepareToEnterProcess(isProtectedComponent(createData.info),
+ s.getAttributionSource());
+ try {
+ if (!data.rebind) {
+ IBinder binder = s.onBind(data.intent);
+ ActivityManager.getService().publishService(
+ data.token, data.intent, binder);
+ } else {
+ s.onRebind(data.intent);
+ ActivityManager.getService().serviceDoneExecuting(
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(s, e)) {
+ throw new RuntimeException(
+ "Unable to bind to service " + s
+ + " with " + data.intent + ": " + e.toString(), e);
+ }
+ }
+ }
+ }
+
+ private void handleUnbindService(BindServiceData data) {
+ CreateServiceData createData = mServicesData.get(data.token);
+ Service s = mServices.get(data.token);
+ if (s != null) {
+ try {
+ data.intent.setExtrasClassLoader(s.getClassLoader());
+ data.intent.prepareToEnterProcess(isProtectedComponent(createData.info),
+ s.getAttributionSource());
+ boolean doRebind = s.onUnbind(data.intent);
+ try {
+ if (doRebind) {
+ ActivityManager.getService().unbindFinished(
+ data.token, data.intent, doRebind);
+ } else {
+ ActivityManager.getService().serviceDoneExecuting(
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(s, e)) {
+ throw new RuntimeException(
+ "Unable to unbind to service " + s
+ + " with " + data.intent + ": " + e.toString(), e);
+ }
+ }
+ }
+ }
+
+ private void handleDumpGfxInfo(DumpComponentInfo info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ ThreadedRenderer.handleDumpGfxInfo(info.fd.getFileDescriptor(), info.args);
+ } catch (Exception e) {
+ Log.w(TAG, "Caught exception from dumpGfxInfo()", e);
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ private void handleDumpService(DumpComponentInfo info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ Service s = mServices.get(info.token);
+ if (s != null) {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ info.fd.getFileDescriptor()));
+ s.dump(info.fd.getFileDescriptor(), pw, info.args);
+ pw.flush();
+ }
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ private void handleDumpResources(DumpResourcesData info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ info.fd.getFileDescriptor()));
+
+ Resources.dumpHistory(pw, "");
+ pw.flush();
+ if (info.finishCallback != null) {
+ info.finishCallback.sendResult(null);
+ }
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ private void handleDumpActivity(DumpComponentInfo info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ ActivityClientRecord r = mActivities.get(info.token);
+ if (r != null && r.activity != null) {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ info.fd.getFileDescriptor()));
+ r.activity.dumpInternal(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
+ pw.flush();
+ }
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ private void handleDumpProvider(DumpComponentInfo info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ ProviderClientRecord r = mLocalProviders.get(info.token);
+ if (r != null && r.mLocalProvider != null) {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ info.fd.getFileDescriptor()));
+ r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args);
+ pw.flush();
+ }
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ private void handleServiceArgs(ServiceArgsData data) {
+ CreateServiceData createData = mServicesData.get(data.token);
+ Service s = mServices.get(data.token);
+ if (s != null) {
+ try {
+ if (data.args != null) {
+ data.args.setExtrasClassLoader(s.getClassLoader());
+ data.args.prepareToEnterProcess(isProtectedComponent(createData.info),
+ s.getAttributionSource());
+ }
+ int res;
+ if (!data.taskRemoved) {
+ res = s.onStartCommand(data.args, data.flags, data.startId);
+ } else {
+ s.onTaskRemoved(data.args);
+ res = Service.START_TASK_REMOVED_COMPLETE;
+ }
+
+ QueuedWork.waitToFinish();
+
+ try {
+ ActivityManager.getService().serviceDoneExecuting(
+ data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(s, e)) {
+ throw new RuntimeException(
+ "Unable to start service " + s
+ + " with " + data.args + ": " + e.toString(), e);
+ }
+ }
+ }
+ }
+
+ private void handleStopService(IBinder token) {
+ mServicesData.remove(token);
+ Service s = mServices.remove(token);
+ if (s != null) {
+ try {
+ if (localLOGV) Slog.v(TAG, "Destroying service " + s);
+ s.onDestroy();
+ s.detachAndCleanUp();
+ Context context = s.getBaseContext();
+ if (context instanceof ContextImpl) {
+ final String who = s.getClassName();
+ ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
+ }
+
+ QueuedWork.waitToFinish();
+
+ try {
+ ActivityManager.getService().serviceDoneExecuting(
+ token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(s, e)) {
+ throw new RuntimeException(
+ "Unable to stop service " + s
+ + ": " + e.toString(), e);
+ }
+ Slog.i(TAG, "handleStopService: exception for " + token, e);
+ }
+ } else {
+ Slog.i(TAG, "handleStopService: token=" + token + " not found.");
+ }
+ //Slog.i(TAG, "Running services: " + mServices);
+ }
+
+ private void handleTimeoutService(IBinder token, int startId) {
+ Service s = mServices.get(token);
+ if (s != null) {
+ try {
+ if (localLOGV) Slog.v(TAG, "Timeout short service " + s);
+
+ // Unlike other service callbacks, we don't do serviceDoneExecuting() here.
+ // "service executing" state is used to boost the procstate / oom-adj, but
+ // for short-FGS timeout, we have a specific control for them anyway, so
+ // we don't have to do that.
+ s.callOnTimeout(startId);
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(s, e)) {
+ throw new RuntimeException(
+ "Unable to call onTimeout on service " + s
+ + ": " + e.toString(), e);
+ }
+ Slog.i(TAG, "handleTimeoutService: exception for " + token, e);
+ }
+ } else {
+ Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found.");
+ }
+ }
+ /**
+ * Resume the activity.
+ * @param r Target activity record.
+ * @param finalStateRequest Flag indicating if this is part of final state resolution for a
+ * transaction.
+ * @param reason Reason for performing the action.
+ *
+ * @return {@code true} that was resumed, {@code false} otherwise.
+ */
+ @VisibleForTesting
+ public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
+ String reason) {
+ if (localLOGV) {
+ Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
+ }
+ if (r.activity.mFinished) {
+ return false;
+ }
+ if (r.getLifecycleState() == ON_RESUME) {
+ if (!finalStateRequest) {
+ final RuntimeException e = new IllegalStateException(
+ "Trying to resume activity which is already resumed");
+ Slog.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, r.getStateString());
+ // TODO(lifecycler): A double resume request is possible when an activity
+ // receives two consequent transactions with relaunch requests and "resumed"
+ // final state requests and the second relaunch is omitted. We still try to
+ // handle two resume requests for the final state. For cases other than this
+ // one, we don't expect it to happen.
+ }
+ return false;
+ }
+ if (finalStateRequest) {
+ r.hideForNow = false;
+ r.activity.mStartedActivity = false;
+ }
+ try {
+ r.activity.onStateNotSaved();
+ r.activity.mFragments.noteStateNotSaved();
+ checkAndBlockForNetworkAccess();
+ if (r.pendingIntents != null) {
+ deliverNewIntents(r, r.pendingIntents);
+ r.pendingIntents = null;
+ }
+ if (r.pendingResults != null) {
+ deliverResults(r, r.pendingResults, reason);
+ r.pendingResults = null;
+ }
+ r.activity.performResume(r.startsNotResumed, reason);
+
+ r.state = null;
+ r.persistentState = null;
+ r.setState(ON_RESUME);
+
+ reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException("Unable to resume activity "
+ + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
+ }
+ }
+ return true;
+ }
+
+ static final void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) {
+ if (r.mPreserveWindow && !force) {
+ return;
+ }
+ if (r.mPendingRemoveWindow != null) {
+ r.mPendingRemoveWindowManager.removeViewImmediate(
+ r.mPendingRemoveWindow.getDecorView());
+ IBinder wtoken = r.mPendingRemoveWindow.getDecorView().getWindowToken();
+ if (wtoken != null) {
+ WindowManagerGlobal.getInstance().closeAll(wtoken,
+ r.activity.getClass().getName(), "Activity");
+ }
+ }
+ r.mPendingRemoveWindow = null;
+ r.mPendingRemoveWindowManager = null;
+ }
+
+ @Override
+ public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
+ boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
+ // If we are getting ready to gc after going to the background, well
+ // we are back active so skip it.
+ unscheduleGcIdler();
+ mSomeActivitiesChanged = true;
+
+ // TODO Push resumeArgs into the activity for consideration
+ // skip below steps for double-resume and r.mFinish = true case.
+ if (!performResumeActivity(r, finalStateRequest, reason)) {
+ return;
+ }
+ if (mActivitiesToBeDestroyed.containsKey(r.token)) {
+ // Although the activity is resumed, it is going to be destroyed. So the following
+ // UI operations are unnecessary and also prevents exception because its token may
+ // be gone that window manager cannot recognize it. All necessary cleanup actions
+ // performed below will be done while handling destruction.
+ return;
+ }
+
+ final Activity a = r.activity;
+
+ if (localLOGV) {
+ Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
+ + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
+ }
+
+ final int forwardBit = isForward
+ ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
+
+ // If the window hasn't yet been added to the window manager,
+ // and this guy didn't finish itself or start another activity,
+ // then go ahead and add the window.
+ boolean willBeVisible = !a.mStartedActivity;
+ if (!willBeVisible) {
+ willBeVisible = ActivityClient.getInstance().willActivityBeVisible(
+ a.getActivityToken());
+ }
+ if (r.window == null && !a.mFinished && willBeVisible) {
+ r.window = r.activity.getWindow();
+ View decor = r.window.getDecorView();
+ decor.setVisibility(View.INVISIBLE);
+ ViewManager wm = a.getWindowManager();
+ WindowManager.LayoutParams l = r.window.getAttributes();
+ a.mDecor = decor;
+ l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+ l.softInputMode |= forwardBit;
+ if (r.mPreserveWindow) {
+ a.mWindowAdded = true;
+ r.mPreserveWindow = false;
+ // Normally the ViewRoot sets up callbacks with the Activity
+ // in addView->ViewRootImpl#setView. If we are instead reusing
+ // the decor view we have to notify the view root that the
+ // callbacks may have changed.
+ ViewRootImpl impl = decor.getViewRootImpl();
+ if (impl != null) {
+ impl.notifyChildRebuilt();
+ }
+ }
+ if (a.mVisibleFromClient) {
+ if (!a.mWindowAdded) {
+ a.mWindowAdded = true;
+ wm.addView(decor, l);
+ } else {
+ // The activity will get a callback for this {@link LayoutParams} change
+ // earlier. However, at that time the decor will not be set (this is set
+ // in this method), so no action will be taken. This call ensures the
+ // callback occurs with the decor set.
+ a.onWindowAttributesChanged(l);
+ }
+ }
+
+ // If the window has already been added, but during resume
+ // we started another activity, then don't yet make the
+ // window visible.
+ } else if (!willBeVisible) {
+ if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
+ r.hideForNow = true;
+ }
+
+ // Get rid of anything left hanging around.
+ cleanUpPendingRemoveWindows(r, false /* force */);
+
+ // The window is now visible if it has been added, we are not
+ // simply finishing, and we are not starting another activity.
+ if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
+ if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
+ ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
+ WindowManager.LayoutParams l = impl != null
+ ? impl.mWindowAttributes : r.window.getAttributes();
+ if ((l.softInputMode
+ & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
+ != forwardBit) {
+ l.softInputMode = (l.softInputMode
+ & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
+ | forwardBit;
+ if (r.activity.mVisibleFromClient) {
+ ViewManager wm = a.getWindowManager();
+ View decor = r.window.getDecorView();
+ wm.updateViewLayout(decor, l);
+ }
+ }
+
+ r.activity.mVisibleFromServer = true;
+ mNumVisibleActivities++;
+ if (r.activity.mVisibleFromClient) {
+ r.activity.makeVisible();
+ }
+
+ if (shouldSendCompatFakeFocus) {
+ // Attaching to a window is asynchronous with the activity being resumed,
+ // so it's possible we will need to send a fake focus event after attaching
+ if (impl != null) {
+ impl.dispatchCompatFakeFocus();
+ } else {
+ r.window.getDecorView().fakeFocusAfterAttachingToWindow();
+ }
+ }
+ }
+
+ mNewActivities.add(r);
+ if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
+ Looper.myQueue().addIdleHandler(new Idler());
+ }
+
+
+ @Override
+ public void handleTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
+ String reason) {
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
+ }
+
+ if (r.isTopResumedActivity == onTop) {
+ if (!Build.IS_DEBUGGABLE) {
+ Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
+ return;
+ }
+ // TODO(b/209744518): Remove this short-term workaround while fixing the binder failure.
+ Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
+ }
+
+ r.isTopResumedActivity = onTop;
+
+ if (r.getLifecycleState() == ON_RESUME) {
+ reportTopResumedActivityChanged(r, onTop, "topStateChangedWhenResumed");
+ } else {
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState());
+ }
+ }
+ }
+
+ /**
+ * Call {@link Activity#onTopResumedActivityChanged(boolean)} if its top resumed state changed
+ * since the last report.
+ */
+ private void reportTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
+ String reason) {
+ if (r.lastReportedTopResumedState != onTop) {
+ r.lastReportedTopResumedState = onTop;
+ r.activity.performTopResumedActivityChanged(onTop, reason);
+ }
+ }
+
+ @Override
+ public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
+ int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions,
+ String reason) {
+ if (userLeaving) {
+ performUserLeavingActivity(r);
+ }
+
+ r.activity.mConfigChangeFlags |= configChanges;
+ if (autoEnteringPip) {
+ // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
+ // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
+ r.activity.mIsInPictureInPictureMode = true;
+ }
+ performPauseActivity(r, finished, reason, pendingActions);
+
+ // Make sure any pending writes are now committed.
+ if (r.isPreHoneycomb()) {
+ QueuedWork.waitToFinish();
+ }
+ mSomeActivitiesChanged = true;
+ }
+
+ final void performUserLeavingActivity(ActivityClientRecord r) {
+ mInstrumentation.callActivityOnPictureInPictureRequested(r.activity);
+ mInstrumentation.callActivityOnUserLeaving(r.activity);
+ }
+
+ final Bundle performPauseActivity(IBinder token, boolean finished, String reason,
+ PendingTransactionActions pendingActions) {
+ ActivityClientRecord r = mActivities.get(token);
+ return r != null ? performPauseActivity(r, finished, reason, pendingActions) : null;
+ }
+
+ /**
+ * Pause the activity.
+ * @return Saved instance state for pre-Honeycomb apps if it was saved, {@code null} otherwise.
+ */
+ private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason,
+ PendingTransactionActions pendingActions) {
+ if (r.paused) {
+ if (r.activity.mFinished) {
+ // If we are finishing, we won't call onResume() in certain cases.
+ // So here we likewise don't want to call onPause() if the activity
+ // isn't resumed.
+ return null;
+ }
+ RuntimeException e = new RuntimeException(
+ "Performing pause of activity that is not resumed: "
+ + r.intent.getComponent().toShortString());
+ Slog.e(TAG, e.getMessage(), e);
+ }
+ if (finished) {
+ r.activity.mFinished = true;
+ }
+
+ // Pre-Honeycomb apps always save their state before pausing
+ final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb();
+ if (shouldSaveState) {
+ callActivityOnSaveInstanceState(r);
+ }
+
+ performPauseActivityIfNeeded(r, reason);
+
+ // Notify any outstanding on paused listeners
+ ArrayList<OnActivityPausedListener> listeners;
+ synchronized (mOnPauseListeners) {
+ listeners = mOnPauseListeners.remove(r.activity);
+ }
+ int size = (listeners != null ? listeners.size() : 0);
+ for (int i = 0; i < size; i++) {
+ listeners.get(i).onPaused(r.activity);
+ }
+
+ final Bundle oldState = pendingActions != null ? pendingActions.getOldState() : null;
+ if (oldState != null) {
+ // We need to keep around the original state, in case we need to be created again.
+ // But we only do this for pre-Honeycomb apps, which always save their state when
+ // pausing, so we can not have them save their state when restarting from a paused
+ // state. For HC and later, we want to (and can) let the state be saved as the
+ // normal part of stopping the activity.
+ if (r.isPreHoneycomb()) {
+ r.state = oldState;
+ }
+ }
+
+ return shouldSaveState ? r.state : null;
+ }
+
+ private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
+ if (r.paused) {
+ // You are already paused silly...
+ return;
+ }
+
+ // Always reporting top resumed position loss when pausing an activity. If necessary, it
+ // will be restored in performResumeActivity().
+ reportTopResumedActivityChanged(r, false /* onTop */, "pausing");
+
+ try {
+ r.activity.mCalled = false;
+ mInstrumentation.callActivityOnPause(r.activity);
+ if (!r.activity.mCalled) {
+ throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
+ + " did not call through to super.onPause()");
+ }
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException("Unable to pause activity "
+ + safeToComponentShortString(r.intent) + ": " + e.toString(), e);
+ }
+ }
+ r.setState(ON_PAUSE);
+ }
+
+ // TODO(b/176961850): Make LocalActivityManager call performStopActivityInner. We cannot remove
+ // this since it's a high usage hidden API.
+ /** Called from {@link LocalActivityManager}. */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 176961850,
+ publicAlternatives = "{@code N/A}")
+ final void performStopActivity(IBinder token, boolean saveState, String reason) {
+ ActivityClientRecord r = mActivities.get(token);
+ performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
+ reason);
+ }
+
+ private static final class ProviderRefCount {
+ public final ContentProviderHolder holder;
+ public final ProviderClientRecord client;
+ public int stableCount;
+ public int unstableCount;
+
+ // When this is set, the stable and unstable ref counts are 0 and
+ // we have a pending operation scheduled to remove the ref count
+ // from the activity manager. On the activity manager we are still
+ // holding an unstable ref, though it is not reflected in the counts
+ // here.
+ public boolean removePending;
+
+ ProviderRefCount(ContentProviderHolder inHolder,
+ ProviderClientRecord inClient, int sCount, int uCount) {
+ holder = inHolder;
+ client = inClient;
+ stableCount = sCount;
+ unstableCount = uCount;
+ }
+ }
+
+ /**
+ * Core implementation of stopping an activity.
+ * @param r Target activity client record.
+ * @param info Action that will report activity stop to server.
+ * @param saveState Flag indicating whether the activity state should be saved.
+ * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+ * request for a transaction.
+ * @param reason Reason for performing this operation.
+ */
+ private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
+ boolean saveState, boolean finalStateRequest, String reason) {
+ if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
+ if (r.stopped) {
+ if (r.activity.mFinished) {
+ // If we are finishing, we won't call onResume() in certain
+ // cases. So here we likewise don't want to call onStop()
+ // if the activity isn't resumed.
+ return;
+ }
+ if (!finalStateRequest) {
+ final RuntimeException e = new RuntimeException(
+ "Performing stop of activity that is already stopped: "
+ + r.intent.getComponent().toShortString());
+ Slog.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, r.getStateString());
+ }
+ }
+
+ // One must first be paused before stopped...
+ performPauseActivityIfNeeded(r, reason);
+
+ if (info != null) {
+ try {
+ // First create a thumbnail for the activity...
+ // For now, don't create the thumbnail here; we are
+ // doing that by doing a screen snapshot.
+ info.setDescription(r.activity.onCreateDescription());
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to save state of activity "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
+ }
+ }
+ }
+
+ callActivityOnStop(r, saveState, reason);
+ }
+
+ /**
+ * Calls {@link Activity#onStop()} and {@link Activity#onSaveInstanceState(Bundle)}, and updates
+ * the client record's state.
+ * All calls to stop an activity must be done through this method to make sure that
+ * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call.
+ */
+ private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
+ // Before P onSaveInstanceState was called before onStop, starting with P it's
+ // called after. Before Honeycomb state was always saved before onPause.
+ final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
+ && !r.isPreHoneycomb();
+ final boolean isPreP = r.isPreP();
+ if (shouldSaveState && isPreP) {
+ callActivityOnSaveInstanceState(r);
+ }
+
+ try {
+ r.activity.performStop(r.mPreserveWindow, reason);
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to stop activity "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
+ }
+ }
+ r.setState(ON_STOP);
+
+ if (shouldSaveState && !isPreP) {
+ callActivityOnSaveInstanceState(r);
+ }
+ }
+
+ private void updateVisibility(ActivityClientRecord r, boolean show) {
+ View v = r.activity.mDecor;
+ if (v != null) {
+ if (show) {
+ if (!r.activity.mVisibleFromServer) {
+ r.activity.mVisibleFromServer = true;
+ mNumVisibleActivities++;
+ if (r.activity.mVisibleFromClient) {
+ r.activity.makeVisible();
+ }
+ }
+ } else {
+ if (r.activity.mVisibleFromServer) {
+ r.activity.mVisibleFromServer = false;
+ mNumVisibleActivities--;
+ v.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleStopActivity(ActivityClientRecord r, int configChanges,
+ PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
+ r.activity.mConfigChangeFlags |= configChanges;
+
+ final StopInfo stopInfo = new StopInfo();
+ performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
+ reason);
+
+ if (localLOGV) Slog.v(
+ TAG, "Finishing stop of " + r + ": win=" + r.window);
+
+ updateVisibility(r, false);
+
+ // Make sure any pending writes are now committed.
+ if (!r.isPreHoneycomb()) {
+ QueuedWork.waitToFinish();
+ }
+
+ stopInfo.setActivity(r);
+ stopInfo.setState(r.state);
+ stopInfo.setPersistentState(r.persistentState);
+ pendingActions.setStopInfo(stopInfo);
+ mSomeActivitiesChanged = true;
+ }
+
+ /**
+ * Schedule the call to tell the activity manager we have stopped. We don't do this
+ * immediately, because we want to have a chance for any other pending work (in particular
+ * memory trim requests) to complete before you tell the activity manager to proceed and allow
+ * us to go fully into the background.
+ */
+ @Override
+ public void reportStop(PendingTransactionActions pendingActions) {
+ mH.post(pendingActions.getStopInfo());
+ }
+
+ @Override
+ public void performRestartActivity(ActivityClientRecord r, boolean start) {
+ if (r.stopped) {
+ r.activity.performRestart(start);
+ if (start) {
+ r.setState(ON_START);
+ }
+ }
+ }
+
+ @Override
+ public void reportRefresh(ActivityClientRecord r) {
+ ActivityClient.getInstance().activityRefreshed(r.token);
+ }
+
+ private void handleSetCoreSettings(Bundle coreSettings) {
+ synchronized (mCoreSettingsLock) {
+ mCoreSettings = coreSettings;
+ }
+ onCoreSettingsChange();
+ }
+
+ private void onCoreSettingsChange() {
+ if (updateDebugViewAttributeState()) {
+ // request all activities to relaunch for the changes to take place
+ relaunchAllActivities(true /* preserveWindows */, "onCoreSettingsChange");
+ }
+ }
+
+ private boolean updateDebugViewAttributeState() {
+ boolean previousState = View.sDebugViewAttributes;
+
+ // mCoreSettings is only updated from the main thread, while this function is only called
+ // from main thread as well, so no need to lock here.
+ View.sDebugViewAttributesApplicationPackage = mCoreSettings.getString(
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, "");
+ String currentPackage = (mBoundApplication != null && mBoundApplication.appInfo != null)
+ ? mBoundApplication.appInfo.packageName : "<unknown-app>";
+ View.sDebugViewAttributes =
+ mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0
+ || View.sDebugViewAttributesApplicationPackage.equals(currentPackage);
+ return previousState != View.sDebugViewAttributes;
+ }
+
+ private void relaunchAllActivities(boolean preserveWindows, String reason) {
+ Log.i(TAG, "Relaunch all activities: " + reason);
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ scheduleRelaunchActivityIfPossible(mActivities.valueAt(i), preserveWindows);
+ }
+ }
+
+ private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
+ mCompatibilityInfo = data.info;
+ LoadedApk apk = peekPackageInfo(data.pkg, false);
+ if (apk != null) {
+ apk.setCompatibilityInfo(data.info);
+ }
+ apk = peekPackageInfo(data.pkg, true);
+ if (apk != null) {
+ apk.setCompatibilityInfo(data.info);
+ }
+ mConfigurationController.handleConfigurationChanged(data.info);
+ }
+
+ private void deliverResults(ActivityClientRecord r, List<ResultInfo> results, String reason) {
+ final int N = results.size();
+ for (int i=0; i<N; i++) {
+ ResultInfo ri = results.get(i);
+ try {
+ if (ri.mData != null) {
+ ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
+ ri.mData.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
+ r.activity.getAttributionSource());
+ }
+ if (DEBUG_RESULTS) Slog.v(TAG,
+ "Delivering result to activity " + r + " : " + ri);
+ r.activity.dispatchActivityResult(ri.mResultWho,
+ ri.mRequestCode, ri.mResultCode, ri.mData, reason);
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Failure delivering result " + ri + " to activity "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleSendResult(ActivityClientRecord r, List<ResultInfo> results, String reason) {
+ if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
+ final boolean resumed = !r.paused;
+ if (!r.activity.mFinished && r.activity.mDecor != null
+ && r.hideForNow && resumed) {
+ // We had hidden the activity because it started another
+ // one... we have gotten a result back and we are not
+ // paused, so make sure our window is visible.
+ updateVisibility(r, true);
+ }
+ if (resumed) {
+ try {
+ // Now we are idle.
+ r.activity.mCalled = false;
+ mInstrumentation.callActivityOnPause(r.activity);
+ if (!r.activity.mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + r.intent.getComponent().toShortString()
+ + " did not call through to super.onPause()");
+ }
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to pause activity "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
+ }
+ }
+ }
+ checkAndBlockForNetworkAccess();
+ deliverResults(r, results, reason);
+ if (resumed) {
+ r.activity.performResume(false, reason);
+ }
+ }
+
+ /** Core implementation of activity destroy call. */
+ void performDestroyActivity(ActivityClientRecord r, boolean finishing,
+ int configChanges, boolean getNonConfigInstance, String reason) {
+ Class<? extends Activity> activityClass = null;
+ if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
+ activityClass = r.activity.getClass();
+ r.activity.mConfigChangeFlags |= configChanges;
+ if (finishing) {
+ r.activity.mFinished = true;
+ }
+
+ performPauseActivityIfNeeded(r, "destroy");
+
+ if (!r.stopped) {
+ callActivityOnStop(r, false /* saveState */, "destroy");
+ }
+ if (getNonConfigInstance) {
+ try {
+ r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException("Unable to retain activity "
+ + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
+ }
+ }
+ }
+ try {
+ r.activity.mCalled = false;
+ mInstrumentation.callActivityOnDestroy(r.activity);
+ if (!r.activity.mCalled) {
+ throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
+ + " did not call through to super.onDestroy()");
+ }
+ if (r.window != null) {
+ r.window.closeAllPanels();
+ }
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException("Unable to destroy activity "
+ + safeToComponentShortString(r.intent) + ": " + e.toString(), e);
+ }
+ }
+ r.setState(ON_DESTROY);
+ schedulePurgeIdler();
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.tokenDestroyed(r.token);
+ }
+ }
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.remove(r.token);
+ }
+ StrictMode.decrementExpectedActivityCount(activityClass);
+ }
+
+ private static String safeToComponentShortString(Intent intent) {
+ ComponentName component = intent.getComponent();
+ return component == null ? "[Unknown]" : component.toShortString();
+ }
+
+ @Override
+ public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+ return mActivitiesToBeDestroyed;
+ }
+
+ @Override
+ public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
+ boolean getNonConfigInstance, String reason) {
+ performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+ cleanUpPendingRemoveWindows(r, finishing);
+ WindowManager wm = r.activity.getWindowManager();
+ View v = r.activity.mDecor;
+ if (v != null) {
+ if (r.activity.mVisibleFromServer) {
+ mNumVisibleActivities--;
+ }
+ IBinder wtoken = v.getWindowToken();
+ if (r.activity.mWindowAdded) {
+ if (r.mPreserveWindow) {
+ // Hold off on removing this until the new activity's window is being added.
+ r.mPendingRemoveWindow = r.window;
+ r.mPendingRemoveWindowManager = wm;
+ // We can only keep the part of the view hierarchy that we control,
+ // everything else must be removed, because it might not be able to
+ // behave properly when activity is relaunching.
+ r.window.clearContentView();
+ } else {
+ final ViewRootImpl viewRoot = v.getViewRootImpl();
+ if (viewRoot != null) {
+ // Clear callbacks to avoid the destroyed activity from receiving
+ // configuration or camera compat changes that are no longer effective.
+ viewRoot.setActivityConfigCallback(null);
+ }
+ wm.removeViewImmediate(v);
+ }
+ }
+ if (wtoken != null && r.mPendingRemoveWindow == null) {
+ WindowManagerGlobal.getInstance().closeAll(wtoken,
+ r.activity.getClass().getName(), "Activity");
+ } else if (r.mPendingRemoveWindow != null) {
+ // We're preserving only one window, others should be closed so app views
+ // will be detached before the final tear down. It should be done now because
+ // some components (e.g. WebView) rely on detach callbacks to perform receiver
+ // unregister and other cleanup.
+ WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
+ r.activity.getClass().getName(), "Activity");
+ }
+ r.activity.mDecor = null;
+ }
+ if (r.mPendingRemoveWindow == null) {
+ // If we are delaying the removal of the activity window, then
+ // we can't clean up all windows here. Note that we can't do
+ // so later either, which means any windows that aren't closed
+ // by the app will leak. Well we try to warning them a lot
+ // about leaking windows, because that is a bug, so if they are
+ // using this recreate facility then they get to live with leaks.
+ WindowManagerGlobal.getInstance().closeAll(r.token,
+ r.activity.getClass().getName(), "Activity");
+ }
+
+ // Mocked out contexts won't be participating in the normal
+ // process lifecycle, but if we're running with a proper
+ // ApplicationContext we need to have it tear down things
+ // cleanly.
+ Context c = r.activity.getBaseContext();
+ if (c instanceof ContextImpl) {
+ ((ContextImpl) c).scheduleFinalCleanup(r.activity.getClass().getName(), "Activity");
+ }
+ if (finishing) {
+ ActivityClient.getInstance().activityDestroyed(r.token);
+ mNewActivities.remove(r);
+ }
+ mSomeActivitiesChanged = true;
+ }
+
+ @Override
+ public ActivityClientRecord prepareRelaunchActivity(IBinder token,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ int configChanges, MergedConfiguration config, boolean preserveWindow) {
+ ActivityClientRecord target = null;
+ boolean scheduleRelaunch = false;
+
+ synchronized (mResourcesManager) {
+ for (int i=0; i<mRelaunchingActivities.size(); i++) {
+ ActivityClientRecord r = mRelaunchingActivities.get(i);
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + this + ", trying: " + r);
+ if (r.token == token) {
+ target = r;
+ if (pendingResults != null) {
+ if (r.pendingResults != null) {
+ r.pendingResults.addAll(pendingResults);
+ } else {
+ r.pendingResults = pendingResults;
+ }
+ }
+ if (pendingNewIntents != null) {
+ if (r.pendingIntents != null) {
+ r.pendingIntents.addAll(pendingNewIntents);
+ } else {
+ r.pendingIntents = pendingNewIntents;
+ }
+ }
+ break;
+ }
+ }
+
+ if (target == null) {
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null");
+ target = new ActivityClientRecord();
+ target.token = token;
+ target.pendingResults = pendingResults;
+ target.pendingIntents = pendingNewIntents;
+ target.mPreserveWindow = preserveWindow;
+ mRelaunchingActivities.add(target);
+ scheduleRelaunch = true;
+ }
+ target.createdConfig = config.getGlobalConfiguration();
+ target.overrideConfig = config.getOverrideConfiguration();
+ target.pendingConfigChanges |= configChanges;
+ }
+
+ return scheduleRelaunch ? target : null;
+ }
+
+ @Override
+ public void handleRelaunchActivity(ActivityClientRecord tmp,
+ PendingTransactionActions pendingActions) {
+ // If we are getting ready to gc after going to the background, well
+ // we are back active so skip it.
+ unscheduleGcIdler();
+ mSomeActivitiesChanged = true;
+
+ int configChanges = 0;
+
+ // First: make sure we have the most recent configuration and most
+ // recent version of the activity, or skip it if some previous call
+ // had taken a more recent version.
+ synchronized (mResourcesManager) {
+ int N = mRelaunchingActivities.size();
+ IBinder token = tmp.token;
+ tmp = null;
+ for (int i=0; i<N; i++) {
+ ActivityClientRecord r = mRelaunchingActivities.get(i);
+ if (r.token == token) {
+ tmp = r;
+ configChanges |= tmp.pendingConfigChanges;
+ mRelaunchingActivities.remove(i);
+ i--;
+ N--;
+ }
+ }
+
+ if (tmp == null) {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Abort, activity not relaunching!");
+ return;
+ }
+
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+ + tmp.token + " with configChanges=0x"
+ + Integer.toHexString(configChanges));
+ }
+
+ Configuration changedConfig = mConfigurationController.getPendingConfiguration(
+ true /* clearPending */);
+ mPendingConfiguration = null;
+
+ if (tmp.createdConfig != null) {
+ // If the activity manager is passing us its current config,
+ // assume that is really what we want regardless of what we
+ // may have pending.
+ final Configuration config = mConfigurationController.getConfiguration();
+ if (config == null
+ || (tmp.createdConfig.isOtherSeqNewer(config)
+ && config.diff(tmp.createdConfig) != 0)) {
+ if (changedConfig == null
+ || tmp.createdConfig.isOtherSeqNewer(changedConfig)) {
+ changedConfig = tmp.createdConfig;
+ }
+ }
+ }
+
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+ + tmp.token + ": changedConfig=" + changedConfig);
+
+ // If there was a pending configuration change, execute it first.
+ if (changedConfig != null) {
+ mConfigurationController.updateDefaultDensity(changedConfig.densityDpi);
+ mConfigurationController.handleConfigurationChanged(changedConfig, null);
+
+ // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
+ mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
+ mConfiguration = mConfigurationController.getConfiguration();
+ }
+
+ ActivityClientRecord r = mActivities.get(tmp.token);
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
+ if (r == null) {
+ return;
+ }
+
+ r.activity.mConfigChangeFlags |= configChanges;
+ r.mPreserveWindow = tmp.mPreserveWindow;
+
+ r.activity.mChangingConfigurations = true;
+
+ handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+ pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
+ }
+
+ void scheduleRelaunchActivity(IBinder token) {
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r != null) {
+ Log.i(TAG, "Schedule relaunch activity: " + r.activityInfo.name);
+ scheduleRelaunchActivityIfPossible(r, !r.stopped /* preserveWindow */);
+ }
+ }
+
+ /**
+ * Post a message to relaunch the activity. We do this instead of launching it immediately,
+ * because this will destroy the activity from which it was called and interfere with the
+ * lifecycle changes it was going through before. We need to make sure that we have finished
+ * handling current transaction item before relaunching the activity.
+ */
+ private void scheduleRelaunchActivityIfPossible(@NonNull ActivityClientRecord r,
+ boolean preserveWindow) {
+ if ((r.activity != null && r.activity.mFinished) || r.token instanceof Binder) {
+ // Do not schedule relaunch if the activity is finishing or is a local object (e.g.
+ // created by ActivtiyGroup that server side doesn't recognize it).
+ return;
+ }
+ if (preserveWindow && r.window != null) {
+ r.mPreserveWindow = true;
+ }
+ mH.removeMessages(H.RELAUNCH_ACTIVITY, r.token);
+ sendMessage(H.RELAUNCH_ACTIVITY, r.token);
+ }
+
+ /** Performs the activity relaunch locally vs. requesting from system-server. */
+ public void handleRelaunchActivityLocally(IBinder token) {
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r == null) {
+ Log.w(TAG, "Activity to relaunch no longer exists");
+ return;
+ }
+
+ final int prevState = r.getLifecycleState();
+
+ if (prevState < ON_START || prevState > ON_STOP) {
+ Log.w(TAG, "Activity state must be in [ON_START..ON_STOP] in order to be relaunched,"
+ + "current state is " + prevState);
+ return;
+ }
+
+ ActivityClient.getInstance().activityLocalRelaunch(r.token);
+ // Initialize a relaunch request.
+ final MergedConfiguration mergedConfiguration = new MergedConfiguration(
+ r.createdConfig != null
+ ? r.createdConfig : mConfigurationController.getConfiguration(),
+ r.overrideConfig);
+ final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
+ null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */,
+ mergedConfiguration, r.mPreserveWindow);
+ // Make sure to match the existing lifecycle state in the end of the transaction.
+ final ActivityLifecycleItem lifecycleRequest =
+ TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
+ // Schedule the transaction.
+ final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+ transaction.addCallback(activityRelaunchItem);
+ transaction.setLifecycleStateRequest(lifecycleRequest);
+ executeTransaction(transaction);
+ }
+
+ private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
+ PendingTransactionActions pendingActions, boolean startsNotResumed,
+ Configuration overrideConfig, String reason) {
+ // Preserve last used intent, it may be set from Activity#setIntent().
+ final Intent customIntent = r.activity.mIntent;
+ // Need to ensure state is saved.
+ if (!r.paused) {
+ performPauseActivity(r, false, reason, null /* pendingActions */);
+ }
+ if (!r.stopped) {
+ callActivityOnStop(r, true /* saveState */, reason);
+ }
+
+ handleDestroyActivity(r, false, configChanges, true, reason);
+
+ r.activity = null;
+ r.window = null;
+ r.hideForNow = false;
+ // Merge any pending results and pending intents; don't just replace them
+ if (pendingResults != null) {
+ if (r.pendingResults == null) {
+ r.pendingResults = pendingResults;
+ } else {
+ r.pendingResults.addAll(pendingResults);
+ }
+ }
+ if (pendingIntents != null) {
+ if (r.pendingIntents == null) {
+ r.pendingIntents = pendingIntents;
+ } else {
+ r.pendingIntents.addAll(pendingIntents);
+ }
+ }
+ r.startsNotResumed = startsNotResumed;
+ r.overrideConfig = overrideConfig;
+
+ handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
+ }
+
+ @Override
+ public void reportRelaunch(ActivityClientRecord r) {
+ ActivityClient.getInstance().activityRelaunched(r.token);
+ }
+
+ private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
+ r.state = new Bundle();
+ r.state.setAllowFds(false);
+ if (r.isPersistable()) {
+ r.persistentState = new PersistableBundle();
+ mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
+ r.persistentState);
+ } else {
+ mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
+ }
+ }
+
+ @Override
+ public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts) {
+ ArrayList<ComponentCallbacks2> callbacks
+ = new ArrayList<ComponentCallbacks2>();
+
+ synchronized (mResourcesManager) {
+ final int NAPP = mAllApplications.size();
+ for (int i=0; i<NAPP; i++) {
+ callbacks.add(mAllApplications.get(i));
+ }
+ if (includeUiContexts) {
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ final Activity a = mActivities.valueAt(i).activity;
+ if (a != null && !a.mFinished) {
+ callbacks.add(a);
+ }
+ }
+ }
+ final int NSVC = mServices.size();
+ for (int i=0; i<NSVC; i++) {
+ final Service service = mServices.valueAt(i);
+ // If {@code includeUiContext} is set to false, WindowProviderService should not be
+ // collected because WindowProviderService is a UI Context.
+ if (includeUiContexts || !(service instanceof WindowProviderService)) {
+ callbacks.add(service);
+ }
+ }
+ }
+ synchronized (mProviderMap) {
+ final int NPRV = mLocalProviders.size();
+ for (int i=0; i<NPRV; i++) {
+ callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);
+ }
+ }
+
+ return callbacks;
+ }
+
+ /**
+ * Updates the configuration for an Activity. The ActivityClientRecord's
+ * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
+ * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
+ * the updated Configuration.
+ * @param r ActivityClientRecord representing the Activity.
+ * @param newBaseConfig The new configuration to use. This may be augmented with
+ * {@link ActivityClientRecord#overrideConfig}.
+ * @param displayId The id of the display where the Activity currently resides.
+ * @return {@link Configuration} instance sent to client, null if not sent.
+ */
+ private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
+ Configuration newBaseConfig, int displayId, boolean alwaysReportChange) {
+ r.tmpConfig.setTo(newBaseConfig);
+ if (r.overrideConfig != null) {
+ r.tmpConfig.updateFrom(r.overrideConfig);
+ }
+ final Configuration reportedConfig = performActivityConfigurationChanged(r,
+ r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+ return reportedConfig;
+ }
+
+ /**
+ * Decides whether to update an Activity's configuration and whether to inform it.
+ * @param r The activity client record to notify of configuration change.
+ * @param newConfig The new configuration.
+ * @param amOverrideConfig The override config that differentiates the Activity's configuration
+ * from the base global configuration. This is supplied by
+ * ActivityManager.
+ * @param displayId Id of the display where activity currently resides.
+ * @return Configuration sent to client, null if no changes and not moved to different display.
+ */
+ private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
+ Configuration newConfig, Configuration amOverrideConfig, int displayId,
+ boolean alwaysReportChange) {
+ final Activity activity = r.activity;
+ final IBinder activityToken = activity.getActivityToken();
+
+ // WindowConfiguration differences aren't considered as public, check it separately.
+ // multi-window / pip mode changes, if any, should be sent before the configuration
+ // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
+ handleWindowingModeChangeIfNeeded(r, newConfig);
+
+ final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
+ displayId);
+ final Configuration currentResConfig = activity.getResources().getConfiguration();
+ final int diff = currentResConfig.diffPublicOnly(newConfig);
+ final boolean hasPublicResConfigChange = diff != 0;
+ // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
+ // ResourcesImpl constructions.
+ final boolean shouldUpdateResources = hasPublicResConfigChange
+ || shouldUpdateResources(activityToken, currentResConfig, newConfig,
+ amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
+ final boolean shouldReportChange = shouldReportChange(
+ activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
+ activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
+ // Nothing significant, don't proceed with updating and reporting.
+ if (!shouldUpdateResources && !shouldReportChange) {
+ return null;
+ }
+
+ // Propagate the configuration change to ResourcesManager and Activity.
+
+ // ContextThemeWrappers may override the configuration for that context. We must check and
+ // apply any overrides defined.
+ Configuration contextThemeWrapperOverrideConfig = activity.getOverrideConfiguration();
+
+ // We only update an Activity's configuration if this is not a global configuration change.
+ // This must also be done before the callback, or else we violate the contract that the new
+ // resources are available in ComponentCallbacks2#onConfigurationChanged(Configuration).
+ // Also apply the ContextThemeWrapper override if necessary.
+ // NOTE: Make sure the configurations are not modified, as they are treated as immutable in
+ // many places.
+ final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
+ amOverrideConfig, contextThemeWrapperOverrideConfig);
+ mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
+
+ // Apply the ContextThemeWrapper override if necessary.
+ // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+ // in many places.
+ final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
+ contextThemeWrapperOverrideConfig);
+
+ if (movedToDifferentDisplay) {
+ activity.dispatchMovedToDisplay(displayId, configToReport);
+ }
+
+ activity.mConfigChangeFlags = 0;
+ if (shouldReportChange) {
+ activity.mCalled = false;
+ activity.mCurrentConfig = new Configuration(newConfig);
+ activity.onConfigurationChanged(configToReport);
+ if (!activity.mCalled) {
+ throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
+ " did not call through to super.onConfigurationChanged()");
+ }
+ }
+
+ return configToReport;
+ }
+
+ /**
+ * Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
+ * dispatched.
+ *
+ * @param currentConfig The current configuration cached in {@link Activity#mCurrentConfig}.
+ * It is {@code null} before the first config update from the server side.
+ * @param newConfig The updated {@link Configuration}
+ * @param sizeBuckets The Activity's {@link SizeConfigurationBuckets} if not {@code null}
+ * @param handledConfigChanges Bit mask of configuration changes that the activity can handle
+ * @return {@code true} if the config change should be reported to the Activity
+ */
+ @VisibleForTesting
+ public static boolean shouldReportChange(@Nullable Configuration currentConfig,
+ @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
+ int handledConfigChanges, boolean alwaysReportChange) {
+ // Always report changes in window configuration bounds
+ if (shouldUpdateWindowMetricsBounds(currentConfig, newConfig)) {
+ return true;
+ }
+
+ final int publicDiff = currentConfig.diffPublicOnly(newConfig);
+ // Don't report the change if there's no public diff between current and new config.
+ if (publicDiff == 0) {
+ return false;
+ }
+
+ // Report the change regardless if the changes across size-config-buckets.
+ if (alwaysReportChange) {
+ return true;
+ }
+
+ final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,
+ newConfig, sizeBuckets);
+ // Compare to the diff which filter the change without crossing size buckets with
+ // {@code handledConfigChanges}. The small changes should not block Activity to receive
+ // its handled config updates. Also, if Activity handles all small changes, we should
+ // dispatch the updated config to it.
+ final int diff = diffWithBucket != 0 ? diffWithBucket : publicDiff;
+ // If this activity doesn't handle any of the config changes, then don't bother
+ // calling onConfigurationChanged. Otherwise, report to the activity for the
+ // changes.
+ return (~handledConfigChanges & diff) == 0;
+ }
+
+ public final void applyConfigurationToResources(Configuration config) {
+ synchronized (mResourcesManager) {
+ mResourcesManager.applyConfigurationToResources(config, null);
+ }
+ }
+
+ private void updateDeviceIdForNonUIContexts(int deviceId) {
+ // Invalid device id is treated as a no-op.
+ if (deviceId == Context.DEVICE_ID_INVALID) {
+ return;
+ }
+ if (deviceId == mLastReportedDeviceId) {
+ return;
+ }
+ mLastReportedDeviceId = deviceId;
+ ArrayList<Context> nonUIContexts = new ArrayList<>();
+ // Update Application and Service contexts with implicit device association.
+ // UI Contexts are able to derived their device Id association from the display.
+ synchronized (mResourcesManager) {
+ final int numApps = mAllApplications.size();
+ for (int i = 0; i < numApps; i++) {
+ nonUIContexts.add(mAllApplications.get(i));
+ }
+ final int numServices = mServices.size();
+ for (int i = 0; i < numServices; i++) {
+ final Service service = mServices.valueAt(i);
+ // WindowProviderService is a UI Context.
+ if (!service.isUiContext()) {
+ nonUIContexts.add(service);
+ }
+ }
+ }
+ for (Context context : nonUIContexts) {
+ try {
+ context.updateDeviceId(deviceId);
+ } catch (IllegalArgumentException e) {
+ // It can happen that the system already closed/removed a virtual device
+ // and the passed deviceId is no longer valid.
+ // TODO(b/263355088): check for validity of deviceId before updating
+ // instead of catching this exception once VDM add an API to validate ids.
+ }
+ }
+ }
+
+ @Override
+ public void handleConfigurationChanged(Configuration config, int deviceId) {
+ mConfigurationController.handleConfigurationChanged(config);
+ updateDeviceIdForNonUIContexts(deviceId);
+
+ // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
+ mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
+ mConfiguration = mConfigurationController.getConfiguration();
+ mPendingConfiguration = mConfigurationController.getPendingConfiguration(
+ false /* clearPending */);
+ }
+
+ /**
+ * Sends windowing mode change callbacks to {@link Activity} if applicable.
+ *
+ * See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and
+ * {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)}
+ */
+ private void handleWindowingModeChangeIfNeeded(ActivityClientRecord r,
+ Configuration newConfiguration) {
+ final Activity activity = r.activity;
+ final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();
+ final int oldWindowingMode = r.mLastReportedWindowingMode;
+ if (oldWindowingMode == newWindowingMode) return;
+ // PiP callback is sent before the MW one.
+ if (newWindowingMode == WINDOWING_MODE_PINNED) {
+ activity.dispatchPictureInPictureModeChanged(true, newConfiguration);
+ } else if (oldWindowingMode == WINDOWING_MODE_PINNED) {
+ activity.dispatchPictureInPictureModeChanged(false, newConfiguration);
+ }
+ final boolean wasInMultiWindowMode = WindowConfiguration.inMultiWindowMode(
+ oldWindowingMode);
+ final boolean nowInMultiWindowMode = WindowConfiguration.inMultiWindowMode(
+ newWindowingMode);
+ if (wasInMultiWindowMode != nowInMultiWindowMode) {
+ activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
+ }
+ r.mLastReportedWindowingMode = newWindowingMode;
+ }
+
+ /**
+ * Updates the application info.
+ *
+ * This only works in the system process. Must be called on the main thread.
+ */
+ public void handleSystemApplicationInfoChanged(@NonNull ApplicationInfo ai) {
+ Preconditions.checkState(mSystemThread, "Must only be called in the system process");
+ handleApplicationInfoChanged(ai);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
+ // Updates triggered by package installation go through a package update
+ // receiver. Here we try to capture ApplicationInfo changes that are
+ // caused by other sources, such as overlays. That means we want to be as conservative
+ // about code changes as possible. Take the diff of the old ApplicationInfo and the new
+ // to see if anything needs to change.
+ LoadedApk apk;
+ LoadedApk resApk;
+ // Update all affected loaded packages with new package information
+ synchronized (mResourcesManager) {
+ WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
+ apk = ref != null ? ref.get() : null;
+ ref = mResourcePackages.get(ai.packageName);
+ resApk = ref != null ? ref.get() : null;
+ }
+
+ if (apk != null) {
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
+ apk.updateApplicationInfo(ai, oldPaths);
+ }
+ if (resApk != null) {
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
+ resApk.updateApplicationInfo(ai, oldPaths);
+ }
+
+ synchronized (mResourcesManager) {
+ // Update all affected Resources objects to use new ResourcesImpl
+ mResourcesManager.applyAllPendingAppInfoUpdates();
+ }
+ }
+
+ /**
+ * Sets the supplied {@code overrideConfig} as pending for the {@code token}. Calling
+ * this method prevents any calls to
+ * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int)} from
+ * processing any configurations older than {@code overrideConfig}.
+ */
+ @Override
+ public void updatePendingActivityConfiguration(IBinder token, Configuration overrideConfig) {
+ synchronized (mPendingOverrideConfigs) {
+ final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(token);
+ if (pendingOverrideConfig != null
+ && !pendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity has newer configuration pending so this transaction will"
+ + " be dropped. overrideConfig=" + overrideConfig
+ + " pendingOverrideConfig=" + pendingOverrideConfig);
+ }
+ return;
+ }
+ mPendingOverrideConfigs.put(token, overrideConfig);
+ }
+ }
+
+ @Override
+ public void handleActivityConfigurationChanged(ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId) {
+ handleActivityConfigurationChanged(r, overrideConfig, displayId,
+ // This is the only place that uses alwaysReportChange=true. The entry point should
+ // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
+ // has confirmed the activity should handle the configuration instead of relaunch.
+ // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
+ // something wrong from server side.
+ true /* alwaysReportChange */);
+ }
+
+ /**
+ * Handle new activity configuration and/or move to a different display. This method is a noop
+ * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been
+ * called with a newer config than {@code overrideConfig}.
+ *
+ * @param r Target activity record.
+ * @param overrideConfig Activity override config.
+ * @param displayId Id of the display where activity was moved to, -1 if there was no move and
+ * value didn't change.
+ */
+ void handleActivityConfigurationChanged(ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
+ synchronized (mPendingOverrideConfigs) {
+ final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
+ if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity has newer configuration pending so drop this"
+ + " transaction. overrideConfig=" + overrideConfig
+ + " pendingOverrideConfig=" + pendingOverrideConfig);
+ }
+ return;
+ }
+ mPendingOverrideConfigs.remove(r.token);
+ }
+
+ if (displayId == INVALID_DISPLAY) {
+ // If INVALID_DISPLAY is passed assume that the activity should keep its current
+ // display.
+ displayId = r.activity.getDisplayId();
+ }
+ final boolean movedToDifferentDisplay = isDifferentDisplay(
+ r.activity.getDisplayId(), displayId);
+ if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
+ && !movedToDifferentDisplay) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity already handled newer configuration so drop this"
+ + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
+ + r.overrideConfig);
+ }
+ return;
+ }
+
+ // Perform updates.
+ r.overrideConfig = overrideConfig;
+ final ViewRootImpl viewRoot = r.activity.mDecor != null
+ ? r.activity.mDecor.getViewRootImpl() : null;
+
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Handle activity config changed, activity:"
+ + r.activityInfo.name + ", displayId=" + r.activity.getDisplayId()
+ + (movedToDifferentDisplay ? (", newDisplayId=" + displayId) : "")
+ + ", config=" + overrideConfig);
+ }
+ final Configuration reportedConfig = performConfigurationChangedForActivity(r,
+ mConfigurationController.getCompatConfiguration(),
+ movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),
+ alwaysReportChange);
+ // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
+ // update to make sure that resources are updated before updating itself.
+ if (viewRoot != null) {
+ if (movedToDifferentDisplay) {
+ viewRoot.onMovedToDisplay(displayId, reportedConfig);
+ }
+ viewRoot.updateConfiguration(displayId);
+ }
+ mSomeActivitiesChanged = true;
+ }
+
+ final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
+ if (start) {
+ try {
+ switch (profileType) {
+ default:
+ mProfiler.setProfiler(profilerInfo);
+ mProfiler.startProfiling();
+ break;
+ }
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
+ + " -- can the process access this path?");
+ } finally {
+ profilerInfo.closeFd();
+ }
+ } else {
+ switch (profileType) {
+ default:
+ mProfiler.stopProfiling();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Public entrypoint to stop profiling. This is required to end profiling when the app crashes,
+ * so that profiler data won't be lost.
+ *
+ * @hide
+ */
+ public void stopProfiling() {
+ if (mProfiler != null) {
+ mProfiler.stopProfiling();
+ }
+ }
+
+ static void handleDumpHeap(DumpHeapData dhd) {
+ if (dhd.runGc) {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+ try (ParcelFileDescriptor fd = dhd.fd) {
+ if (dhd.managed) {
+ Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
+ } else if (dhd.mallocInfo) {
+ Debug.dumpNativeMallocInfo(fd.getFileDescriptor());
+ } else {
+ Debug.dumpNativeHeap(fd.getFileDescriptor());
+ }
+ } catch (IOException e) {
+ if (dhd.managed) {
+ Slog.w(TAG, "Managed heap dump failed on path " + dhd.path
+ + " -- can the process access this path?", e);
+ } else {
+ Slog.w(TAG, "Failed to dump heap", e);
+ }
+ } catch (RuntimeException e) {
+ // This should no longer happening now that we're copying the file descriptor.
+ Slog.wtf(TAG, "Heap dumper threw a runtime exception", e);
+ }
+ try {
+ ActivityManager.getService().dumpHeapFinished(dhd.path);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (dhd.finishCallback != null) {
+ dhd.finishCallback.sendResult(null);
+ }
+ }
+
+ final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
+ boolean hasPkgInfo = false;
+ switch (cmd) {
+ case ApplicationThreadConstants.PACKAGE_REMOVED:
+ case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL:
+ {
+ final boolean killApp = cmd == ApplicationThreadConstants.PACKAGE_REMOVED;
+ if (packages == null) {
+ break;
+ }
+ synchronized (mResourcesManager) {
+ for (int i = packages.length - 1; i >= 0; i--) {
+ if (!hasPkgInfo) {
+ WeakReference<LoadedApk> ref = mPackages.get(packages[i]);
+ if (ref != null && ref.get() != null) {
+ hasPkgInfo = true;
+ } else {
+ ref = mResourcePackages.get(packages[i]);
+ if (ref != null && ref.get() != null) {
+ hasPkgInfo = true;
+ }
+ }
+ }
+ if (killApp) {
+ mPackages.remove(packages[i]);
+ mResourcePackages.remove(packages[i]);
+ }
+ }
+ }
+ break;
+ }
+ case ApplicationThreadConstants.PACKAGE_REPLACED:
+ {
+ if (packages == null) {
+ break;
+ }
+
+ List<String> packagesHandled = new ArrayList<>();
+
+ synchronized (mResourcesManager) {
+ for (int i = packages.length - 1; i >= 0; i--) {
+ String packageName = packages[i];
+ WeakReference<LoadedApk> ref = mPackages.get(packageName);
+ LoadedApk pkgInfo = ref != null ? ref.get() : null;
+ if (pkgInfo != null) {
+ hasPkgInfo = true;
+ } else {
+ ref = mResourcePackages.get(packageName);
+ pkgInfo = ref != null ? ref.get() : null;
+ if (pkgInfo != null) {
+ hasPkgInfo = true;
+ }
+ }
+ // If the package is being replaced, yet it still has a valid
+ // LoadedApk object, the package was updated with _DONT_KILL.
+ // Adjust it's internal references to the application info and
+ // resources.
+ if (pkgInfo != null) {
+ packagesHandled.add(packageName);
+ try {
+ final ApplicationInfo aInfo =
+ sPackageManager.getApplicationInfo(
+ packageName,
+ PackageManager.GET_SHARED_LIBRARY_FILES,
+ UserHandle.myUserId());
+
+ if (mActivities.size() > 0) {
+ for (ActivityClientRecord ar : mActivities.values()) {
+ if (ar.activityInfo.applicationInfo.packageName
+ .equals(packageName)) {
+ ar.activityInfo.applicationInfo = aInfo;
+ ar.packageInfo = pkgInfo;
+ }
+ }
+ }
+
+ final String[] oldResDirs = { pkgInfo.getResDir() };
+
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, pkgInfo.getApplicationInfo(), oldPaths);
+ pkgInfo.updateApplicationInfo(aInfo, oldPaths);
+
+ synchronized (mResourcesManager) {
+ // Update affected Resources objects to use new ResourcesImpl
+ mResourcesManager.appendPendingAppInfoUpdate(oldResDirs,
+ aInfo);
+ mResourcesManager.applyAllPendingAppInfoUpdates();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ try {
+ getPackageManager().notifyPackagesReplacedReceived(
+ packagesHandled.toArray(new String[0]));
+ } catch (RemoteException ignored) {
+ }
+
+ break;
+ }
+ }
+ ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo);
+ }
+
+ final void handleLowMemory() {
+ final ArrayList<ComponentCallbacks2> callbacks =
+ collectComponentCallbacks(true /* includeUiContexts */);
+
+ final int N = callbacks.size();
+ for (int i=0; i<N; i++) {
+ callbacks.get(i).onLowMemory();
+ }
+
+ // Ask SQLite to free up as much memory as it can, mostly from its page caches.
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ int sqliteReleased = SQLiteDatabase.releaseMemory();
+ EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
+ }
+
+ // Ask graphics to free up as much as possible (font/image caches)
+ Canvas.freeCaches();
+
+ // Ask text layout engine to free also as much as possible
+ Canvas.freeTextLayoutCaches();
+
+ BinderInternal.forceGc("mem");
+ }
+
+ private void handleTrimMemory(int level) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory: " + level);
+ }
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
+
+ try {
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+ PropertyInvalidatedCache.onTrimMemory();
+ }
+
+ final ArrayList<ComponentCallbacks2> callbacks =
+ collectComponentCallbacks(true /* includeUiContexts */);
+
+ final int N = callbacks.size();
+ for (int i = 0; i < N; i++) {
+ callbacks.get(i).onTrimMemory(level);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ WindowManagerGlobal.getInstance().trimMemory(level);
+
+ if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
+ unscheduleGcIdler();
+ doGcIfNeeded("tm");
+ }
+ if (SystemProperties.getInt("debug.am.run_mallopt_trim_level", Integer.MAX_VALUE)
+ <= level) {
+ unschedulePurgeIdler();
+ purgePendingResources();
+ }
+ }
+
+ private void setupGraphicsSupport(Context context) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupGraphicsSupport");
+
+ // The system package doesn't have real data directories, so don't set up cache paths.
+ if (!"android".equals(context.getPackageName())) {
+ // This cache location probably points at credential-encrypted
+ // storage which may not be accessible yet; assign it anyway instead
+ // of pointing at device-encrypted storage.
+ final File cacheDir = context.getCacheDir();
+ if (cacheDir != null) {
+ // Provide a usable directory for temporary files
+ String tmpdir = cacheDir.getAbsolutePath();
+ System.setProperty("java.io.tmpdir", tmpdir);
+ try {
+ android.system.Os.setenv("TMPDIR", tmpdir, true);
+ } catch (ErrnoException ex) {
+ Log.w(TAG, "Unable to initialize $TMPDIR", ex);
+ }
+ } else {
+ Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property "
+ + "due to missing cache directory");
+ }
+
+ // Setup a location to store generated/compiled graphics code.
+ final Context deviceContext = context.createDeviceProtectedStorageContext();
+ final File codeCacheDir = deviceContext.getCodeCacheDir();
+ final File deviceCacheDir = deviceContext.getCacheDir();
+ if (codeCacheDir != null && deviceCacheDir != null) {
+ try {
+ int uid = Process.myUid();
+ String[] packages = getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ HardwareRenderer.setupDiskCache(deviceCacheDir);
+ RenderScriptCacheDir.setupDiskCache(codeCacheDir);
+ }
+ } catch (RemoteException e) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Log.w(TAG, "Unable to use shader/script cache: missing code-cache directory");
+ }
+ }
+
+ // mCoreSettings is only updated from the main thread, while this function is only called
+ // from main thread as well, so no need to lock here.
+ GraphicsEnvironment.getInstance().setup(context, mCoreSettings);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /**
+ * Returns the correct library directory for the current ABI.
+ * <p>
+ * If we're dealing with a multi-arch application that has both 32 and 64 bit shared
+ * libraries, we might need to choose the secondary depending on what the current
+ * runtime's instruction set is.
+ */
+ private String getInstrumentationLibrary(ApplicationInfo appInfo, InstrumentationInfo insInfo) {
+ if (appInfo.primaryCpuAbi != null && appInfo.secondaryCpuAbi != null
+ && appInfo.secondaryCpuAbi.equals(insInfo.secondaryCpuAbi)) {
+ // Get the instruction set supported by the secondary ABI. In the presence
+ // of a native bridge this might be different than the one secondary ABI used.
+ String secondaryIsa =
+ VMRuntime.getInstructionSet(appInfo.secondaryCpuAbi);
+ final String secondaryDexCodeIsa =
+ SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa);
+ secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa;
+
+ final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
+ if (runtimeIsa.equals(secondaryIsa)) {
+ return insInfo.secondaryNativeLibraryDir;
+ }
+ }
+ return insInfo.nativeLibraryDir;
+ }
+
+ @UnsupportedAppUsage
+ private void handleBindApplication(AppBindData data) {
+ // Register the UI Thread as a sensitive thread to the runtime.
+ VMRuntime.registerSensitiveThread();
+ // In the case the stack depth property exists, pass it down to the runtime.
+ String property = SystemProperties.get("debug.allocTracker.stackDepth");
+ if (property.length() != 0) {
+ VMDebug.setAllocTrackerStackDepth(Integer.parseInt(property));
+ }
+ if (data.trackAllocation) {
+ DdmVmInternal.setRecentAllocationsTrackingEnabled(true);
+ }
+ // Note when this process has started.
+ Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
+ data.startRequestedElapsedTime, data.startRequestedUptime);
+
+ AppCompatCallbacks.install(data.disabledCompatChanges);
+ // Let libcore handle any compat changes after installing the list of compat changes.
+ AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
+
+ // Initialize the zip path validator callback depending on the targetSdk.
+ // This has to be after AppCompatCallbacks#install() so that the Compat
+ // checks work accordingly.
+ initZipPathValidatorCallback();
+
+ mBoundApplication = data;
+ mConfigurationController.setConfiguration(data.config);
+ mConfigurationController.setCompatConfiguration(data.config);
+ mConfiguration = mConfigurationController.getConfiguration();
+ mCompatibilityInfo = data.compatInfo;
+
+ mProfiler = new Profiler();
+ String agent = null;
+ if (data.initProfilerInfo != null) {
+ mProfiler.profileFile = data.initProfilerInfo.profileFile;
+ mProfiler.profileFd = data.initProfilerInfo.profileFd;
+ mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
+ mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
+ mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
+ mProfiler.mClockType = data.initProfilerInfo.clockType;
+ if (data.initProfilerInfo.attachAgentDuringBind) {
+ agent = data.initProfilerInfo.agent;
+ }
+ }
+
+ // send up app name; do this *before* waiting for debugger
+ Process.setArgV0(data.processName);
+ android.ddm.DdmHandleAppName.setAppName(data.processName,
+ data.appInfo.packageName,
+ UserHandle.myUserId());
+ VMRuntime.setProcessPackageName(data.appInfo.packageName);
+
+ // Pass data directory path to ART. This is used for caching information and
+ // should be set before any application code is loaded.
+ VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);
+
+ if (mProfiler.profileFd != null) {
+ mProfiler.startProfiling();
+ }
+
+ // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
+ // implementation to use the pool executor. Normally, we use the
+ // serialized executor as the default. This has to happen in the
+ // main thread so the main looper is set right.
+ if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
+ AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ // Let the util.*Array classes maintain "undefined" for apps targeting Pie or earlier.
+ UtilConfig.setThrowExceptionForUpperArrayOutOfBounds(
+ data.appInfo.targetSdkVersion >= Build.VERSION_CODES.Q);
+
+ Message.updateCheckRecycle(data.appInfo.targetSdkVersion);
+
+ // Supply the targetSdkVersion to the UI rendering module, which may
+ // need it in cases where it does not have access to the appInfo.
+ android.graphics.Compatibility.setTargetSdkVersion(data.appInfo.targetSdkVersion);
+
+ /*
+ * Before spawning a new process, reset the time zone to be the system time zone.
+ * This needs to be done because the system time zone could have changed after the
+ * the spawning of this process. Without doing this this process would have the incorrect
+ * system time zone.
+ */
+ TimeZone.setDefault(null);
+
+ /*
+ * Set the LocaleList. This may change once we create the App Context.
+ */
+ LocaleList.setDefault(data.config.getLocales());
+
+ if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
+ try {
+ Typeface.setSystemFontMap(data.mSerializedSystemFontMap);
+ } catch (IOException | ErrnoException e) {
+ Slog.e(TAG, "Failed to parse serialized system font map");
+ Typeface.loadPreinstalledSystemFontMap();
+ }
+ }
+
+ synchronized (mResourcesManager) {
+ /*
+ * Update the system configuration since its preloaded and might not
+ * reflect configuration changes. The configuration object passed
+ * in AppBindData can be safely assumed to be up to date
+ */
+ mResourcesManager.applyConfigurationToResources(data.config, data.compatInfo);
+ mCurDefaultDisplayDpi = data.config.densityDpi;
+
+ // This calls mResourcesManager so keep it within the synchronized block.
+ mConfigurationController.applyCompatConfiguration();
+ }
+
+ final boolean isSdkSandbox = data.sdkSandboxClientAppPackage != null;
+ data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
+ false /* securityViolation */, true /* includeCode */,
+ false /* registerPackage */, isSdkSandbox);
+ if (isSdkSandbox) {
+ data.info.setSdkSandboxStorage(data.sdkSandboxClientAppVolumeUuid,
+ data.sdkSandboxClientAppPackage);
+ }
+
+ if (agent != null) {
+ handleAttachAgent(agent, data.info);
+ }
+
+ /**
+ * Switch this process to density compatibility mode if needed.
+ */
+ if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
+ == 0) {
+ mDensityCompatMode = true;
+ Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
+ }
+ mConfigurationController.updateDefaultDensity(data.config.densityDpi);
+
+ // mCoreSettings is only updated from the main thread, while this function is only called
+ // from main thread as well, so no need to lock here.
+ final String use24HourSetting = mCoreSettings.getString(Settings.System.TIME_12_24);
+ Boolean is24Hr = null;
+ if (use24HourSetting != null) {
+ is24Hr = "24".equals(use24HourSetting) ? Boolean.TRUE : Boolean.FALSE;
+ }
+ // null : use locale default for 12/24 hour formatting,
+ // false : use 12 hour format,
+ // true : use 24 hour format.
+ DateFormat.set24HourTimePref(is24Hr);
+
+ updateDebugViewAttributeState();
+
+ StrictMode.initThreadDefaults(data.appInfo);
+ StrictMode.initVmDefaults(data.appInfo);
+
+ // Allow binder tracing, and application-generated systrace messages if we're profileable.
+ boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable();
+ Trace.setAppTracingAllowed(isAppProfileable);
+ if ((isAppProfileable || Build.IS_DEBUGGABLE) && data.enableBinderTracking) {
+ Binder.enableStackTracking();
+ }
+
+ // Initialize heap profiling.
+ if (isAppProfileable || Build.IS_DEBUGGABLE) {
+ nInitZygoteChildHeapProfiling();
+ }
+
+ // Allow renderer debugging features if we're debuggable.
+ HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
+ HardwareRenderer.setPackageName(data.appInfo.packageName);
+
+ // Pass the current context to HardwareRenderer
+ HardwareRenderer.setContextForInit(getSystemContext());
+ if (data.persistent) {
+ HardwareRenderer.setIsSystemOrPersistent();
+ }
+
+ // Instrumentation info affects the class loader, so load it before
+ // setting up the app context.
+ final InstrumentationInfo ii;
+ if (data.instrumentationName != null) {
+ ii = prepareInstrumentation(data);
+ } else {
+ ii = null;
+ }
+
+ final IActivityManager mgr = ActivityManager.getService();
+ final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
+ mConfigurationController.updateLocaleListFromAppContext(appContext);
+
+ // Initialize the default http proxy in this process.
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
+ try {
+ // In pre-boot mode (doing initial launch to collect password), not all system is up.
+ // This includes the connectivity service, so trying to obtain ConnectivityManager at
+ // that point would return null. Check whether the ConnectivityService is available, and
+ // avoid crashing with a NullPointerException if it is not.
+ final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ if (b != null) {
+ final ConnectivityManager cm =
+ appContext.getSystemService(ConnectivityManager.class);
+ Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ if (!Process.isIsolated()) {
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try {
+ setupGraphicsSupport(appContext);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ } else {
+ HardwareRenderer.setIsolatedProcess(true);
+ }
+
+ // Install the Network Security Config Provider. This must happen before the application
+ // code is loaded to prevent issues with instances of TLS objects being created before
+ // the provider is installed.
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "NetworkSecurityConfigProvider.install");
+ NetworkSecurityConfigProvider.install(appContext);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ // For backward compatibility, TrafficStats needs static access to the application context.
+ // But for isolated apps which cannot access network related services, service discovery
+ // is restricted. Hence, calling this would result in NPE.
+ if (!Process.isIsolated()) {
+ TrafficStats.init(appContext);
+ }
+
+ // Continue loading instrumentation.
+ if (ii != null) {
+ initInstrumentation(ii, data, appContext);
+ } else {
+ mInstrumentation = new Instrumentation();
+ mInstrumentation.basicInit(this);
+ }
+
+ if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
+ dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
+ } else {
+ // Small heap, clamp to the current growth limit and let the heap release
+ // pages after the growth limit to the non growth limit capacity. b/18387825
+ dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
+ }
+
+ // Allow disk access during application and provider setup. This could
+ // block processing ordered broadcasts, but later processing would
+ // probably end up doing the same disk access.
+ Application app;
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
+ final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+
+ if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
+ if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
+ waitForDebugger(data);
+ } else if (data.debugMode == ApplicationThreadConstants.DEBUG_SUSPEND) {
+ suspendAllAndSendVmStart(data);
+ }
+ // Nothing special to do in case of DEBUG_ON.
+ }
+
+ try {
+ // If the app is being launched for full backup or restore, bring it up in
+ // a restricted environment with the base application class.
+ app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
+
+ // Propagate autofill compat state
+ app.setAutofillOptions(data.autofillOptions);
+
+ // Propagate Content Capture options
+ app.setContentCaptureOptions(data.contentCaptureOptions);
+ sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);
+
+ mInitialApplication = app;
+ final boolean updateHttpProxy;
+ synchronized (this) {
+ updateHttpProxy = mUpdateHttpProxyOnBind;
+ // This synchronized block ensures that any subsequent call to updateHttpProxy()
+ // will see a non-null mInitialApplication.
+ }
+ if (updateHttpProxy) {
+ ActivityThread.updateHttpProxy(app);
+ }
+
+ // don't bring up providers in restricted mode; they may depend on the
+ // app's custom Application class
+ if (!data.restrictedBackupMode) {
+ if (!ArrayUtils.isEmpty(data.providers)) {
+ installContentProviders(app, data.providers);
+ }
+ }
+
+ // Do this after providers, since instrumentation tests generally start their
+ // test thread at this point, and we don't want that racing.
+ try {
+ mInstrumentation.onCreate(data.instrumentationArgs);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(
+ "Exception thrown in onCreate() of "
+ + data.instrumentationName + ": " + e.toString(), e);
+ }
+ try {
+ mInstrumentation.callApplicationOnCreate(app);
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(app, e)) {
+ throw new RuntimeException(
+ "Unable to create application " + app.getClass().getName()
+ + ": " + e.toString(), e);
+ }
+ }
+ } finally {
+ // If the app targets < O-MR1, or doesn't change the thread policy
+ // during startup, clobber the policy to maintain behavior of b/36951662
+ if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
+ || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+ }
+
+ // Preload fonts resources
+ FontsContract.setApplicationContextForResources(appContext);
+ if (!Process.isIsolated()) {
+ try {
+ final ApplicationInfo info =
+ getPackageManager().getApplicationInfo(
+ data.appInfo.packageName,
+ PackageManager.GET_META_DATA /*flags*/,
+ UserHandle.myUserId());
+ if (info.metaData != null) {
+ final int preloadedFontsResource = info.metaData.getInt(
+ ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
+ if (preloadedFontsResource != 0) {
+ data.info.getResources().preloadFonts(preloadedFontsResource);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ try {
+ mgr.finishAttachApplication(mStartSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ private void waitForDebugger(AppBindData data) {
+ final IActivityManager mgr = ActivityManager.getService();
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ + " is waiting for the debugger ...");
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, true);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ Debug.waitForDebugger();
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, false);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ private void suspendAllAndSendVmStart(AppBindData data) {
+ final IActivityManager mgr = ActivityManager.getService();
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ + " is suspending. Debugger needs to resume to continue.");
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, true);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ Debug.suspendAllAndSendVmStart();
+
+ try {
+ mgr.showWaitingForDebugger(mAppThread, false);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+ * entry names.
+ * Otherwise: clear the callback to the default validation.
+ */
+ private void initZipPathValidatorCallback() {
+ if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+ ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+ } else {
+ ZipPathValidator.clearCallback();
+ }
+ }
+
+ private void handleSetContentCaptureOptionsCallback(String packageName) {
+ if (mContentCaptureOptionsCallback != null) {
+ return;
+ }
+
+ IBinder b = ServiceManager.getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
+ if (b == null) {
+ return;
+ }
+
+ IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b);
+ mContentCaptureOptionsCallback = new IContentCaptureOptionsCallback.Stub() {
+ @Override
+ public void setContentCaptureOptions(ContentCaptureOptions options)
+ throws RemoteException {
+ if (mInitialApplication != null) {
+ mInitialApplication.setContentCaptureOptions(options);
+ }
+ }
+ };
+ try {
+ service.registerContentCaptureOptionsCallback(packageName,
+ mContentCaptureOptionsCallback);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "registerContentCaptureOptionsCallback() failed: "
+ + packageName, e);
+ mContentCaptureOptionsCallback = null;
+ }
+ }
+
+ private void handleInstrumentWithoutRestart(AppBindData data) {
+ try {
+ data.compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ data.info = getPackageInfoNoCheck(data.appInfo);
+ mInstrumentingWithoutRestart = true;
+ final InstrumentationInfo ii = prepareInstrumentation(data);
+ final ContextImpl appContext =
+ ContextImpl.createAppContext(this, data.info);
+
+ initInstrumentation(ii, data, appContext);
+
+ try {
+ mInstrumentation.onCreate(data.instrumentationArgs);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Exception thrown in onCreate() of "
+ + data.instrumentationName + ": " + e.toString(), e);
+ }
+
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in handleInstrumentWithoutRestart", e);
+ }
+ }
+
+ private InstrumentationInfo prepareInstrumentation(AppBindData data) {
+ final InstrumentationInfo ii;
+ try {
+ ii = getPackageManager().getInstrumentationInfoAsUser(data.instrumentationName,
+ 0 /* flags */, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (ii == null) {
+ throw new RuntimeException(
+ "Unable to find instrumentation info for: " + data.instrumentationName);
+ }
+
+ // Warn of potential ABI mismatches.
+ if (!Objects.equals(data.appInfo.primaryCpuAbi, ii.primaryCpuAbi)
+ || !Objects.equals(data.appInfo.secondaryCpuAbi, ii.secondaryCpuAbi)) {
+ Slog.w(TAG, "Package uses different ABI(s) than its instrumentation: "
+ + "package[" + data.appInfo.packageName + "]: "
+ + data.appInfo.primaryCpuAbi + ", " + data.appInfo.secondaryCpuAbi
+ + " instrumentation[" + ii.packageName + "]: "
+ + ii.primaryCpuAbi + ", " + ii.secondaryCpuAbi);
+ }
+
+ mInstrumentationPackageName = ii.packageName;
+ mInstrumentationAppDir = ii.sourceDir;
+ mInstrumentationSplitAppDirs = ii.splitSourceDirs;
+ mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
+ mInstrumentedAppDir = data.info.getAppDir();
+ mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
+ mInstrumentedLibDir = data.info.getLibDir();
+
+ return ii;
+ }
+
+ private void initInstrumentation(
+ InstrumentationInfo ii, AppBindData data, ContextImpl appContext) {
+ ApplicationInfo instrApp;
+ try {
+ instrApp = getPackageManager().getApplicationInfo(ii.packageName, 0,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ instrApp = null;
+ }
+ if (instrApp == null) {
+ instrApp = new ApplicationInfo();
+ }
+ ii.copyTo(instrApp);
+ instrApp.initForUser(UserHandle.myUserId());
+ final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
+ appContext.getClassLoader(), false, true, false);
+
+ // The test context's op package name == the target app's op package name, because
+ // the app ops manager checks the op package name against the real calling UID,
+ // which is what the target package name is associated with.
+ final ContextImpl instrContext = ContextImpl.createAppContext(this, pi,
+ appContext.getOpPackageName());
+
+ try {
+ final ClassLoader cl = instrContext.getClassLoader();
+ mInstrumentation = (Instrumentation)
+ cl.loadClass(data.instrumentationName.getClassName()).newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Unable to instantiate instrumentation "
+ + data.instrumentationName + ": " + e.toString(), e);
+ }
+
+ final ComponentName component = new ComponentName(ii.packageName, ii.name);
+ mInstrumentation.init(this, instrContext, appContext, component,
+ data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
+
+ if (mProfiler.profileFile != null && !ii.handleProfiling
+ && mProfiler.profileFd == null) {
+ mProfiler.handlingProfiling = true;
+ final File file = new File(mProfiler.profileFile);
+ file.getParentFile().mkdirs();
+ Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+ }
+ }
+
+ private void handleFinishInstrumentationWithoutRestart() {
+ mInstrumentation.onDestroy();
+ mInstrumentationPackageName = null;
+ mInstrumentationAppDir = null;
+ mInstrumentationSplitAppDirs = null;
+ mInstrumentationLibDir = null;
+ mInstrumentedAppDir = null;
+ mInstrumentedSplitAppDirs = null;
+ mInstrumentedLibDir = null;
+ mInstrumentingWithoutRestart = false;
+ }
+
+ /*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
+ IActivityManager am = ActivityManager.getService();
+ if (mProfiler.profileFile != null && mProfiler.handlingProfiling
+ && mProfiler.profileFd == null) {
+ Debug.stopMethodTracing();
+ }
+ //Slog.i(TAG, "am: " + ActivityManager.getService()
+ // + ", app thr: " + mAppThread);
+ try {
+ am.finishInstrumentation(mAppThread, resultCode, results);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ if (mInstrumentingWithoutRestart) {
+ sendMessage(H.FINISH_INSTRUMENTATION_WITHOUT_RESTART, null);
+ }
+ }
+
+ @UnsupportedAppUsage
+ private void installContentProviders(
+ Context context, List<ProviderInfo> providers) {
+ final ArrayList<ContentProviderHolder> results = new ArrayList<>();
+
+ for (ProviderInfo cpi : providers) {
+ if (DEBUG_PROVIDER) {
+ StringBuilder buf = new StringBuilder(128);
+ buf.append("Pub ");
+ buf.append(cpi.authority);
+ buf.append(": ");
+ buf.append(cpi.name);
+ Log.i(TAG, buf.toString());
+ }
+ ContentProviderHolder cph = installProvider(context, null, cpi,
+ false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
+ if (cph != null) {
+ cph.noReleaseNeeded = true;
+ results.add(cph);
+ }
+ }
+
+ try {
+ ActivityManager.getService().publishContentProviders(
+ getApplicationThread(), results);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public final IContentProvider acquireProvider(
+ Context c, String auth, int userId, boolean stable) {
+ final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
+ if (provider != null) {
+ return provider;
+ }
+
+ // There is a possible race here. Another thread may try to acquire
+ // the same provider at the same time. When this happens, we want to ensure
+ // that the first one wins.
+ // Note that we cannot hold the lock while acquiring and installing the
+ // provider since it might take a long time to run and it could also potentially
+ // be re-entrant in the case where the provider is in the same process.
+ ContentProviderHolder holder = null;
+ final ProviderKey key = getGetProviderKey(auth, userId);
+ try {
+ synchronized (key) {
+ holder = ActivityManager.getService().getContentProvider(
+ getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
+ // If the returned holder is non-null but its provider is null and it's not
+ // local, we'll need to wait for the publishing of the provider.
+ if (holder != null && holder.provider == null && !holder.mLocal) {
+ synchronized (key.mLock) {
+ if (key.mHolder != null) {
+ if (DEBUG_PROVIDER) {
+ Slog.i(TAG, "already received provider: " + auth);
+ }
+ } else {
+ key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
+ }
+ holder = key.mHolder;
+ }
+ if (holder != null && holder.provider == null) {
+ // probably timed out
+ holder = null;
+ }
+ }
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ } catch (InterruptedException e) {
+ holder = null;
+ } finally {
+ // Clear the holder from the key since the key itself is never cleared.
+ synchronized (key.mLock) {
+ key.mHolder = null;
+ }
+ }
+ if (holder == null) {
+ if (UserManager.get(c).isUserUnlocked(userId)) {
+ Slog.e(TAG, "Failed to find provider info for " + auth);
+ } else {
+ Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)");
+ }
+ return null;
+ }
+
+ // Install provider will increment the reference count for us, and break
+ // any ties in the race.
+ holder = installProvider(c, holder, holder.info,
+ true /*noisy*/, holder.noReleaseNeeded, stable);
+ return holder.provider;
+ }
+
+ private ProviderKey getGetProviderKey(String auth, int userId) {
+ final ProviderKey key = new ProviderKey(auth, userId);
+ synchronized (mGetProviderKeys) {
+ ProviderKey lock = mGetProviderKeys.get(key);
+ if (lock == null) {
+ lock = key;
+ mGetProviderKeys.put(key, lock);
+ }
+ return lock;
+ }
+ }
+
+ private final void incProviderRefLocked(ProviderRefCount prc, boolean stable) {
+ if (stable) {
+ prc.stableCount += 1;
+ if (prc.stableCount == 1) {
+ // We are acquiring a new stable reference on the provider.
+ int unstableDelta;
+ if (prc.removePending) {
+ // We have a pending remove operation, which is holding the
+ // last unstable reference. At this point we are converting
+ // that unstable reference to our new stable reference.
+ unstableDelta = -1;
+ // Cancel the removal of the provider.
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "incProviderRef: stable "
+ + "snatched provider from the jaws of death");
+ }
+ prc.removePending = false;
+ // There is a race! It fails to remove the message, which
+ // will be handled in completeRemoveProvider().
+ mH.removeMessages(H.REMOVE_PROVIDER, prc);
+ } else {
+ unstableDelta = 0;
+ }
+ try {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "incProviderRef Now stable - "
+ + prc.holder.info.name + ": unstableDelta="
+ + unstableDelta);
+ }
+ ActivityManager.getService().refContentProvider(
+ prc.holder.connection, 1, unstableDelta);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+ } else {
+ prc.unstableCount += 1;
+ if (prc.unstableCount == 1) {
+ // We are acquiring a new unstable reference on the provider.
+ if (prc.removePending) {
+ // Oh look, we actually have a remove pending for the
+ // provider, which is still holding the last unstable
+ // reference. We just need to cancel that to take new
+ // ownership of the reference.
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "incProviderRef: unstable "
+ + "snatched provider from the jaws of death");
+ }
+ prc.removePending = false;
+ mH.removeMessages(H.REMOVE_PROVIDER, prc);
+ } else {
+ // First unstable ref, increment our count in the
+ // activity manager.
+ try {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "incProviderRef: Now unstable - "
+ + prc.holder.info.name);
+ }
+ ActivityManager.getService().refContentProvider(
+ prc.holder.connection, 0, 1);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+ }
+ }
+ }
+
+ @UnsupportedAppUsage
+ public final IContentProvider acquireExistingProvider(
+ Context c, String auth, int userId, boolean stable) {
+ synchronized (mProviderMap) {
+ final ProviderKey key = new ProviderKey(auth, userId);
+ final ProviderClientRecord pr = mProviderMap.get(key);
+ if (pr == null) {
+ return null;
+ }
+
+ IContentProvider provider = pr.mProvider;
+ IBinder jBinder = provider.asBinder();
+ if (!jBinder.isBinderAlive()) {
+ // The hosting process of the provider has died; we can't
+ // use this one.
+ Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ + ": existing object's process dead");
+ handleUnstableProviderDiedLocked(jBinder, true);
+ return null;
+ }
+
+ // Only increment the ref count if we have one. If we don't then the
+ // provider is not reference counted and never needs to be released.
+ ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+ if (prc != null) {
+ incProviderRefLocked(prc, stable);
+ }
+ return provider;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public final boolean releaseProvider(IContentProvider provider, boolean stable) {
+ if (provider == null) {
+ return false;
+ }
+
+ IBinder jBinder = provider.asBinder();
+ synchronized (mProviderMap) {
+ ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+ if (prc == null) {
+ // The provider has no ref count, no release is needed.
+ return false;
+ }
+
+ boolean lastRef = false;
+ if (stable) {
+ if (prc.stableCount == 0) {
+ if (DEBUG_PROVIDER) Slog.v(TAG,
+ "releaseProvider: stable ref count already 0, how?");
+ return false;
+ }
+ prc.stableCount -= 1;
+ if (prc.stableCount == 0) {
+ // What we do at this point depends on whether there are
+ // any unstable refs left: if there are, we just tell the
+ // activity manager to decrement its stable count; if there
+ // aren't, we need to enqueue this provider to be removed,
+ // and convert to holding a single unstable ref while
+ // doing so.
+ lastRef = prc.unstableCount == 0;
+ try {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "releaseProvider: No longer stable w/lastRef="
+ + lastRef + " - " + prc.holder.info.name);
+ }
+ ActivityManager.getService().refContentProvider(
+ prc.holder.connection, -1, lastRef ? 1 : 0);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+ } else {
+ if (prc.unstableCount == 0) {
+ if (DEBUG_PROVIDER) Slog.v(TAG,
+ "releaseProvider: unstable ref count already 0, how?");
+ return false;
+ }
+ prc.unstableCount -= 1;
+ if (prc.unstableCount == 0) {
+ // If this is the last reference, we need to enqueue
+ // this provider to be removed instead of telling the
+ // activity manager to remove it at this point.
+ lastRef = prc.stableCount == 0;
+ if (!lastRef) {
+ try {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "releaseProvider: No longer unstable - "
+ + prc.holder.info.name);
+ }
+ ActivityManager.getService().refContentProvider(
+ prc.holder.connection, 0, -1);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+ }
+ }
+
+ if (lastRef) {
+ if (!prc.removePending) {
+ // Schedule the actual remove asynchronously, since we don't know the context
+ // this will be called in.
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "releaseProvider: Enqueueing pending removal - "
+ + prc.holder.info.name);
+ }
+ prc.removePending = true;
+ Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, prc);
+ mH.sendMessageDelayed(msg, CONTENT_PROVIDER_RETAIN_TIME);
+ } else {
+ Slog.w(TAG, "Duplicate remove pending of provider " + prc.holder.info.name);
+ }
+ }
+ return true;
+ }
+ }
+
+ final void completeRemoveProvider(ProviderRefCount prc) {
+ synchronized (mProviderMap) {
+ if (!prc.removePending) {
+ // There was a race! Some other client managed to acquire
+ // the provider before the removal was completed.
+ // Abort the removal. We will do it later.
+ if (DEBUG_PROVIDER) Slog.v(TAG, "completeRemoveProvider: lost the race, "
+ + "provider still in use");
+ return;
+ }
+
+ // More complicated race!! Some client managed to acquire the
+ // provider and release it before the removal was completed.
+ // Continue the removal, and abort the next remove message.
+ prc.removePending = false;
+
+ final IBinder jBinder = prc.holder.provider.asBinder();
+ ProviderRefCount existingPrc = mProviderRefCountMap.get(jBinder);
+ if (existingPrc == prc) {
+ mProviderRefCountMap.remove(jBinder);
+ }
+
+ for (int i=mProviderMap.size()-1; i>=0; i--) {
+ ProviderClientRecord pr = mProviderMap.valueAt(i);
+ IBinder myBinder = pr.mProvider.asBinder();
+ if (myBinder == jBinder) {
+ mProviderMap.removeAt(i);
+ }
+ }
+ }
+
+ try {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "removeProvider: Invoking ActivityManagerService."
+ + "removeContentProvider(" + prc.holder.info.name + ")");
+ }
+ ActivityManager.getService().removeContentProvider(
+ prc.holder.connection, false);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+
+ @UnsupportedAppUsage
+ final void handleUnstableProviderDied(IBinder provider, boolean fromClient) {
+ synchronized (mProviderMap) {
+ handleUnstableProviderDiedLocked(provider, fromClient);
+ }
+ }
+
+ final void handleUnstableProviderDiedLocked(IBinder provider, boolean fromClient) {
+ ProviderRefCount prc = mProviderRefCountMap.get(provider);
+ if (prc != null) {
+ if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider "
+ + provider + " " + prc.holder.info.name);
+ mProviderRefCountMap.remove(provider);
+ for (int i=mProviderMap.size()-1; i>=0; i--) {
+ ProviderClientRecord pr = mProviderMap.valueAt(i);
+ if (pr != null && pr.mProvider.asBinder() == provider) {
+ Slog.i(TAG, "Removing dead content provider:" + pr.mProvider.toString());
+ mProviderMap.removeAt(i);
+ }
+ }
+
+ if (fromClient) {
+ // We found out about this due to execution in our client
+ // code. Tell the activity manager about it now, to ensure
+ // that the next time we go to do anything with the provider
+ // it knows it is dead (so we don't race with its death
+ // notification).
+ try {
+ ActivityManager.getService().unstableProviderDied(
+ prc.holder.connection);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+ }
+ }
+
+ final void appNotRespondingViaProvider(IBinder provider) {
+ synchronized (mProviderMap) {
+ ProviderRefCount prc = mProviderRefCountMap.get(provider);
+ if (prc != null) {
+ try {
+ ActivityManager.getService()
+ .appNotRespondingViaProvider(prc.holder.connection);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
+ ContentProvider localProvider, ContentProviderHolder holder) {
+ final String auths[] = holder.info.authority.split(";");
+ final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
+
+ if (provider != null) {
+ // If this provider is hosted by the core OS and cannot be upgraded,
+ // then I guess we're okay doing blocking calls to it.
+ for (String auth : auths) {
+ switch (auth) {
+ case ContactsContract.AUTHORITY:
+ case CallLog.AUTHORITY:
+ case CallLog.SHADOW_AUTHORITY:
+ case BlockedNumberContract.AUTHORITY:
+ case CalendarContract.AUTHORITY:
+ case Downloads.Impl.AUTHORITY:
+ case "telephony":
+ Binder.allowBlocking(provider.asBinder());
+ }
+ }
+ }
+
+ final ProviderClientRecord pcr = new ProviderClientRecord(
+ auths, provider, localProvider, holder);
+ for (String auth : auths) {
+ final ProviderKey key = new ProviderKey(auth, userId);
+ final ProviderClientRecord existing = mProviderMap.get(key);
+ if (existing != null) {
+ Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
+ + " already published as " + auth);
+ } else {
+ mProviderMap.put(key, pcr);
+ }
+ }
+ return pcr;
+ }
+
+ /**
+ * Installs the provider.
+ *
+ * Providers that are local to the process or that come from the system server
+ * may be installed permanently which is indicated by setting noReleaseNeeded to true.
+ * Other remote providers are reference counted. The initial reference count
+ * for all reference counted providers is one. Providers that are not reference
+ * counted do not have a reference count (at all).
+ *
+ * This method detects when a provider has already been installed. When this happens,
+ * it increments the reference count of the existing provider (if appropriate)
+ * and returns the existing provider. This can happen due to concurrent
+ * attempts to acquire the same provider.
+ */
+ @UnsupportedAppUsage
+ private ContentProviderHolder installProvider(Context context,
+ ContentProviderHolder holder, ProviderInfo info,
+ boolean noisy, boolean noReleaseNeeded, boolean stable) {
+ ContentProvider localProvider = null;
+ IContentProvider provider;
+ if (holder == null || holder.provider == null) {
+ if (DEBUG_PROVIDER || noisy) {
+ Slog.d(TAG, "Loading provider " + info.authority + ": "
+ + info.name);
+ }
+ Context c = null;
+ ApplicationInfo ai = info.applicationInfo;
+ if (context.getPackageName().equals(ai.packageName)) {
+ c = context;
+ } else if (mInitialApplication != null &&
+ mInitialApplication.getPackageName().equals(ai.packageName)) {
+ c = mInitialApplication;
+ } else {
+ try {
+ c = context.createPackageContext(ai.packageName,
+ Context.CONTEXT_INCLUDE_CODE);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
+ }
+ }
+ if (c == null) {
+ Slog.w(TAG, "Unable to get context for package " +
+ ai.packageName +
+ " while loading content provider " +
+ info.name);
+ return null;
+ }
+
+ if (info.splitName != null) {
+ try {
+ c = c.createContextForSplit(info.splitName);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ if (info.attributionTags != null && info.attributionTags.length > 0) {
+ final String attributionTag = info.attributionTags[0];
+ c = c.createAttributionContext(attributionTag);
+ }
+
+ try {
+ final java.lang.ClassLoader cl = c.getClassLoader();
+ LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
+ if (packageInfo == null) {
+ // System startup case.
+ packageInfo = getSystemContext().mPackageInfo;
+ }
+ localProvider = packageInfo.getAppFactory()
+ .instantiateProvider(cl, info.name);
+ provider = localProvider.getIContentProvider();
+ if (provider == null) {
+ Slog.e(TAG, "Failed to instantiate class " +
+ info.name + " from sourceDir " +
+ info.applicationInfo.sourceDir);
+ return null;
+ }
+ if (DEBUG_PROVIDER) Slog.v(
+ TAG, "Instantiating local provider " + info.name);
+ // XXX Need to create the correct context for this provider.
+ localProvider.attachInfo(c, info);
+ } catch (java.lang.Exception e) {
+ if (!mInstrumentation.onException(null, e)) {
+ throw new RuntimeException(
+ "Unable to get provider " + info.name
+ + ": " + e.toString(), e);
+ }
+ return null;
+ }
+ } else {
+ provider = holder.provider;
+ if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
+ + info.name);
+ }
+
+ ContentProviderHolder retHolder;
+
+ synchronized (mProviderMap) {
+ if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
+ + " / " + info.name);
+ IBinder jBinder = provider.asBinder();
+ if (localProvider != null) {
+ ComponentName cname = new ComponentName(info.packageName, info.name);
+ ProviderClientRecord pr = mLocalProvidersByName.get(cname);
+ if (pr != null) {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "installProvider: lost the race, "
+ + "using existing local provider");
+ }
+ provider = pr.mProvider;
+ } else {
+ holder = new ContentProviderHolder(info);
+ holder.provider = provider;
+ holder.noReleaseNeeded = true;
+ pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
+ mLocalProviders.put(jBinder, pr);
+ mLocalProvidersByName.put(cname, pr);
+ }
+ retHolder = pr.mHolder;
+ } else {
+ ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+ if (prc != null) {
+ if (DEBUG_PROVIDER) {
+ Slog.v(TAG, "installProvider: lost the race, updating ref count");
+ }
+ // We need to transfer our new reference to the existing
+ // ref count, releasing the old one... but only if
+ // release is needed (that is, it is not running in the
+ // system process).
+ if (!noReleaseNeeded) {
+ incProviderRefLocked(prc, stable);
+ try {
+ ActivityManager.getService().removeContentProvider(
+ holder.connection, stable);
+ } catch (RemoteException e) {
+ //do nothing content provider object is dead any way
+ }
+ }
+ } else {
+ ProviderClientRecord client = installProviderAuthoritiesLocked(
+ provider, localProvider, holder);
+ if (noReleaseNeeded) {
+ prc = new ProviderRefCount(holder, client, 1000, 1000);
+ } else {
+ prc = stable
+ ? new ProviderRefCount(holder, client, 1, 0)
+ : new ProviderRefCount(holder, client, 0, 1);
+ }
+ mProviderRefCountMap.put(jBinder, prc);
+ }
+ retHolder = prc.holder;
+ }
+ }
+ return retHolder;
+ }
+
+ private void handleRunIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
+ try {
+ Method main = Class.forName(entryPoint).getMethod("main", String[].class);
+ main.invoke(null, new Object[]{entryPointArgs});
+ } catch (ReflectiveOperationException e) {
+ throw new AndroidRuntimeException("runIsolatedEntryPoint failed", e);
+ }
+ // The process will be empty after this method returns; exit the VM now.
+ System.exit(0);
+ }
+
+ @UnsupportedAppUsage
+ private void attach(boolean system, long startSeq) {
+ sCurrentActivityThread = this;
+ mConfigurationController = new ConfigurationController(this);
+ mSystemThread = system;
+ mStartSeq = startSeq;
+
+ if (!system) {
+ android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
+ UserHandle.myUserId());
+ RuntimeInit.setApplicationObject(mAppThread.asBinder());
+ final IActivityManager mgr = ActivityManager.getService();
+ try {
+ mgr.attachApplication(mAppThread, startSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ // Watch for getting close to heap limit.
+ BinderInternal.addGcWatcher(new Runnable() {
+ @Override public void run() {
+ if (!mSomeActivitiesChanged) {
+ return;
+ }
+ Runtime runtime = Runtime.getRuntime();
+ long dalvikMax = runtime.maxMemory();
+ long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
+ if (dalvikUsed > ((3*dalvikMax)/4)) {
+ if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ + " total=" + (runtime.totalMemory()/1024)
+ + " used=" + (dalvikUsed/1024));
+ mSomeActivitiesChanged = false;
+ try {
+ ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ });
+ } else {
+ // Don't set application object here -- if the system crashes,
+ // we can't display an alert, we just want to die die die.
+ android.ddm.DdmHandleAppName.setAppName("system_process",
+ UserHandle.myUserId());
+ try {
+ mInstrumentation = new Instrumentation();
+ mInstrumentation.basicInit(this);
+ ContextImpl context = ContextImpl.createAppContext(
+ this, getSystemContext().mPackageInfo);
+ mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
+ mInitialApplication.onCreate();
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Unable to instantiate Application():" + e.toString(), e);
+ }
+ }
+
+ ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
+ synchronized (mResourcesManager) {
+ // We need to apply this change to the resources immediately, because upon returning
+ // the view hierarchy will be informed about it.
+ if (mResourcesManager.applyConfigurationToResources(globalConfig,
+ null /* compat */)) {
+ mConfigurationController.updateLocaleListFromAppContext(
+ mInitialApplication.getApplicationContext());
+
+ // This actually changed the resources! Tell everyone about it.
+ final Configuration updatedConfig =
+ mConfigurationController.updatePendingConfiguration(globalConfig);
+ if (updatedConfig != null) {
+ sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
+ mPendingConfiguration = updatedConfig;
+ }
+ }
+ }
+ };
+ ViewRootImpl.addConfigCallback(configChangedCallback);
+ }
+
+ @UnsupportedAppUsage
+ public static ActivityThread systemMain() {
+ ThreadedRenderer.initForSystemProcess();
+ ActivityThread thread = new ActivityThread();
+ thread.attach(true, 0);
+ return thread;
+ }
+
+ public static void updateHttpProxy(@NonNull Context context) {
+ final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+ Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
+ }
+
+ @UnsupportedAppUsage
+ public final void installSystemProviders(List<ProviderInfo> providers) {
+ if (providers != null) {
+ installContentProviders(mInitialApplication, providers);
+ }
+ }
+
+ /**
+ * Caller should NEVER mutate the Bundle returned from here
+ */
+ Bundle getCoreSettings() {
+ synchronized (mCoreSettingsLock) {
+ return mCoreSettings;
+ }
+ }
+
+ public int getIntCoreSetting(String key, int defaultValue) {
+ synchronized (mCoreSettingsLock) {
+ if (mCoreSettings != null) {
+ return mCoreSettings.getInt(key, defaultValue);
+ }
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Get the string value of the given key from core settings.
+ */
+ public String getStringCoreSetting(String key, String defaultValue) {
+ synchronized (mCoreSettingsLock) {
+ if (mCoreSettings != null) {
+ return mCoreSettings.getString(key, defaultValue);
+ }
+ return defaultValue;
+ }
+ }
+
+ float getFloatCoreSetting(String key, float defaultValue) {
+ synchronized (mCoreSettingsLock) {
+ if (mCoreSettings != null) {
+ return mCoreSettings.getFloat(key, defaultValue);
+ }
+ return defaultValue;
+ }
+ }
+
+ private static class AndroidOs extends ForwardingOs {
+ /**
+ * Install selective syscall interception. For example, this is used to
+ * implement special filesystem paths that will be redirected to
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}.
+ */
+ public static void install() {
+ // If feature is disabled, we don't need to install
+ if (!DEPRECATE_DATA_COLUMNS) return;
+
+ // Install interception and make sure it sticks!
+ Os def = null;
+ do {
+ def = Os.getDefault();
+ } while (!Os.compareAndSetDefault(def, new AndroidOs(def)));
+ }
+
+ private AndroidOs(Os os) {
+ super(os);
+ }
+
+ private FileDescriptor openDeprecatedDataPath(String path, int mode) throws ErrnoException {
+ final Uri uri = ContentResolver.translateDeprecatedDataPath(path);
+ Log.v(TAG, "Redirecting " + path + " to " + uri);
+
+ final ContentResolver cr = currentActivityThread().getApplication()
+ .getContentResolver();
+ try {
+ final FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(cr.openFileDescriptor(uri,
+ FileUtils.translateModePosixToString(mode)).detachFd());
+ return fd;
+ } catch (SecurityException e) {
+ throw new ErrnoException(e.getMessage(), OsConstants.EACCES);
+ } catch (FileNotFoundException e) {
+ throw new ErrnoException(e.getMessage(), OsConstants.ENOENT);
+ }
+ }
+
+ private void deleteDeprecatedDataPath(String path) throws ErrnoException {
+ final Uri uri = ContentResolver.translateDeprecatedDataPath(path);
+ Log.v(TAG, "Redirecting " + path + " to " + uri);
+
+ final ContentResolver cr = currentActivityThread().getApplication()
+ .getContentResolver();
+ try {
+ if (cr.delete(uri, null, null) == 0) {
+ throw new FileNotFoundException();
+ }
+ } catch (SecurityException e) {
+ throw new ErrnoException(e.getMessage(), OsConstants.EACCES);
+ } catch (FileNotFoundException e) {
+ throw new ErrnoException(e.getMessage(), OsConstants.ENOENT);
+ }
+ }
+
+ @Override
+ public boolean access(String path, int mode) throws ErrnoException {
+ if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+ // If we opened it okay, then access check succeeded
+ IoUtils.closeQuietly(
+ openDeprecatedDataPath(path, FileUtils.translateModeAccessToPosix(mode)));
+ return true;
+ } else {
+ return super.access(path, mode);
+ }
+ }
+
+ @Override
+ public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
+ if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+ return openDeprecatedDataPath(path, mode);
+ } else {
+ return super.open(path, flags, mode);
+ }
+ }
+
+ @Override
+ public StructStat stat(String path) throws ErrnoException {
+ if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+ final FileDescriptor fd = openDeprecatedDataPath(path, OsConstants.O_RDONLY);
+ try {
+ return android.system.Os.fstat(fd);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ } else {
+ return super.stat(path);
+ }
+ }
+
+ @Override
+ public void unlink(String path) throws ErrnoException {
+ if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+ deleteDeprecatedDataPath(path);
+ } else {
+ super.unlink(path);
+ }
+ }
+
+ @Override
+ public void remove(String path) throws ErrnoException {
+ if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+ deleteDeprecatedDataPath(path);
+ } else {
+ super.remove(path);
+ }
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath) throws ErrnoException {
+ try {
+ super.rename(oldPath, newPath);
+ } catch (ErrnoException e) {
+ // On emulated volumes, we have bind mounts for /Android/data and
+ // /Android/obb, which prevents move from working across those directories
+ // and other directories on the filesystem. To work around that, try to
+ // recover by doing a copy instead.
+ // Note that we only do this for "/storage/emulated", because public volumes
+ // don't have these bind mounts, neither do private volumes that are not
+ // the primary storage.
+ if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/emulated")
+ && newPath.startsWith("/storage/emulated")) {
+ Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
+ try {
+ Files.move(new File(oldPath).toPath(), new File(newPath).toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e2) {
+ Log.e(TAG, "Rename recovery failed ", e2);
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
+
+ // Install selective syscall interception
+ AndroidOs.install();
+
+ // CloseGuard defaults to true and can be quite spammy. We
+ // disable it here, but selectively enable it later (via
+ // StrictMode) on debug builds, but using DropBox, not logs.
+ CloseGuard.setEnabled(false);
+
+ Environment.initForCurrentUser();
+
+ // Make sure TrustedCertificateStore looks in the right place for CA certificates
+ final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+ TrustedCertificateStore.setDefaultUserDirectory(configDir);
+
+ // Call per-process mainline module initialization.
+ initializeMainlineModules();
+
+ Process.setArgV0("<pre-initialized>");
+
+ Looper.prepareMainLooper();
+
+ // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
+ // It will be in the format "seq=114"
+ long startSeq = 0;
+ if (args != null) {
+ for (int i = args.length - 1; i >= 0; --i) {
+ if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
+ startSeq = Long.parseLong(
+ args[i].substring(PROC_START_SEQ_IDENT.length()));
+ }
+ }
+ }
+ ActivityThread thread = new ActivityThread();
+ thread.attach(false, startSeq);
+
+ if (sMainThreadHandler == null) {
+ sMainThreadHandler = thread.getHandler();
+ }
+
+ if (false) {
+ Looper.myLooper().setMessageLogging(new
+ LogPrinter(Log.DEBUG, "ActivityThread"));
+ }
+
+ // End of event ActivityThreadMain.
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ Looper.loop();
+
+ throw new RuntimeException("Main thread loop unexpectedly exited");
+ }
+
+ /**
+ * Call various initializer APIs in mainline modules that need to be called when each process
+ * starts.
+ */
+ public static void initializeMainlineModules() {
+ TelephonyFrameworkInitializer.setTelephonyServiceManager(new TelephonyServiceManager());
+ StatsFrameworkInitializer.setStatsServiceManager(new StatsServiceManager());
+ MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
+ MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
+ BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
+ BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
+ BinderCallsStats.startForBluetooth(context);
+ });
+ NfcFrameworkInitializer.setNfcServiceManager(new NfcServiceManager());
+
+ DeviceConfigInitializer.setDeviceConfigServiceManager(new DeviceConfigServiceManager());
+ }
+
+ private void purgePendingResources() {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources");
+ nPurgePendingResources();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /**
+ * Returns whether the provided {@link ActivityInfo} {@code ai} is a protected component.
+ *
+ * @see #isProtectedComponent(ComponentInfo, String)
+ */
+ public static boolean isProtectedComponent(@NonNull ActivityInfo ai) {
+ return isProtectedComponent(ai, ai.permission);
+ }
+
+ /**
+ * Returns whether the provided {@link ServiceInfo} {@code si} is a protected component.
+ *
+ * @see #isProtectedComponent(ComponentInfo, String)
+ */
+ public static boolean isProtectedComponent(@NonNull ServiceInfo si) {
+ return isProtectedComponent(si, si.permission);
+ }
+
+ /**
+ * Returns whether the provided {@link ComponentInfo} {@code ci} with the specified {@code
+ * permission} is a protected component.
+ *
+ * <p>A component is protected if it is not exported, or if the specified {@code permission} is
+ * a signature permission.
+ */
+ private static boolean isProtectedComponent(@NonNull ComponentInfo ci,
+ @Nullable String permission) {
+ // Bail early when this process isn't looking for violations
+ if (!StrictMode.vmUnsafeIntentLaunchEnabled()) return false;
+
+ // TODO: consider optimizing by having AMS pre-calculate this value
+ if (!ci.exported) {
+ return true;
+ }
+ if (permission != null) {
+ try {
+ PermissionInfo pi = getPermissionManager().getPermissionInfo(permission,
+ currentOpPackageName(), 0);
+ return (pi != null) && pi.getProtection() == PermissionInfo.PROTECTION_SIGNATURE;
+ } catch (RemoteException ignored) {
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the action within the provided {@code intent} is a protected broadcast.
+ */
+ public static boolean isProtectedBroadcast(@NonNull Intent intent) {
+ // Bail early when this process isn't looking for violations
+ if (!StrictMode.vmUnsafeIntentLaunchEnabled()) return false;
+
+ // TODO: consider optimizing by having AMS pre-calculate this value
+ try {
+ return getPackageManager().isProtectedBroadcast(intent.getAction());
+ } catch (RemoteException ignored) {
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isInDensityCompatMode() {
+ return mDensityCompatMode;
+ }
+
+ // ------------------ Regular JNI ------------------------
+ private native void nPurgePendingResources();
+ private native void nInitZygoteChildHeapProfiling();
+}
diff --git a/android-34/android/app/ActivityThreadInternal.java b/android-34/android/app/ActivityThreadInternal.java
new file mode 100644
index 0000000..72506b9
--- /dev/null
+++ b/android-34/android/app/ActivityThreadInternal.java
@@ -0,0 +1,38 @@
+/*
+ * 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.app;
+
+import android.content.ComponentCallbacks2;
+
+import java.util.ArrayList;
+
+/**
+ * ActivityThread internal interface.
+ * It is a subset of ActivityThread and used for communicating with
+ * {@link ConfigurationController}.
+ */
+interface ActivityThreadInternal {
+ ContextImpl getSystemContext();
+
+ ContextImpl getSystemUiContextNoCreate();
+
+ boolean isInDensityCompatMode();
+
+ Application getApplication();
+
+ ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts);
+}
diff --git a/android-34/android/app/ActivityTransitionCoordinator.java b/android-34/android/app/ActivityTransitionCoordinator.java
new file mode 100644
index 0000000..f5b3b40
--- /dev/null
+++ b/android-34/android/app/ActivityTransitionCoordinator.java
@@ -0,0 +1,1122 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
+import android.transition.TransitionSet;
+import android.transition.Visibility;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.GhostView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewParent;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.ImageView;
+
+import com.android.internal.view.OneShotPreDrawListener;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
+ * that manage activity transitions and the communications coordinating them between
+ * Activities. The ExitTransitionCoordinator is created in the
+ * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
+ * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
+ * attached.
+ *
+ * Typical startActivity goes like this:
+ * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
+ * 2) Activity#startActivity called and that calls startExit() through
+ * ActivityOptions#dispatchStartExit
+ * - Exit transition starts by setting transitioning Views to INVISIBLE
+ * 3) Launched Activity starts, creating an EnterTransitionCoordinator.
+ * - The Window is made translucent
+ * - The Window background alpha is set to 0
+ * - The transitioning views are made INVISIBLE
+ * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
+ * 4) The shared element transition completes.
+ * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
+ * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator.
+ * - Shared elements are made VISIBLE
+ * - Shared elements positions and size are set to match the end state of the calling
+ * Activity.
+ * - The shared element transition is started
+ * - If the window allows overlapping transitions, the views transition is started by setting
+ * the entering Views to VISIBLE and the background alpha is animated to opaque.
+ * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
+ * - The shared elements are made INVISIBLE
+ * 7) The exit transition completes in the calling Activity.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
+ * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
+ * - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ * by setting entering views to VISIBLE and the background is animated to opaque.
+ * 9) The background opacity animation completes.
+ * - The window is made opaque
+ * 10) The calling Activity gets an onStop() call
+ * - onActivityStopped() is called and all exited Views are made VISIBLE.
+ *
+ * Typical finishAfterTransition goes like this:
+ * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit()
+ * - The Window start transitioning to Translucent with a new ActivityOptions.
+ * - If no background exists, a black background is substituted
+ * - The shared elements in the scene are matched against those shared elements
+ * that were sent by comparing the names.
+ * - The exit transition is started by setting Views to INVISIBLE.
+ * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created.
+ * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
+ * was called
+ * 3) The Window is made translucent and a callback is received
+ * - The background alpha is animated to 0
+ * 4) The background alpha animation completes
+ * 5) The shared element transition completes
+ * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
+ * EnterTransitionCoordinator
+ * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator
+ * - Shared elements are made VISIBLE
+ * - Shared elements positions and size are set to match the end state of the calling
+ * Activity.
+ * - The shared element transition is started
+ * - If the window allows overlapping transitions, the views transition is started by setting
+ * the entering Views to VISIBLE.
+ * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
+ * - The shared elements are made INVISIBLE
+ * 8) The exit transition completes in the finishing Activity.
+ * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
+ * - finish() is called on the exiting Activity
+ * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
+ * - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ * by setting entering views to VISIBLE.
+ */
+abstract class ActivityTransitionCoordinator extends ResultReceiver {
+ private static final String TAG = "ActivityTransitionCoordinator";
+
+ /**
+ * For Activity transitions, the called Activity's listener to receive calls
+ * when transitions complete.
+ */
+ static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver";
+
+ protected static final String KEY_SCREEN_LEFT = "shared_element:screenLeft";
+ protected static final String KEY_SCREEN_TOP = "shared_element:screenTop";
+ protected static final String KEY_SCREEN_RIGHT = "shared_element:screenRight";
+ protected static final String KEY_SCREEN_BOTTOM= "shared_element:screenBottom";
+ protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
+ protected static final String KEY_SNAPSHOT = "shared_element:bitmap";
+ protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
+ protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
+ protected static final String KEY_ELEVATION = "shared_element:elevation";
+
+ protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
+
+ /**
+ * Sent by the exiting coordinator (either EnterTransitionCoordinator
+ * or ExitTransitionCoordinator) after the shared elements have
+ * become stationary (shared element transition completes). This tells
+ * the remote coordinator to take control of the shared elements and
+ * that animations may begin. The remote Activity won't start entering
+ * until this message is received, but may wait for
+ * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
+ */
+ public static final int MSG_SET_REMOTE_RECEIVER = 100;
+
+ /**
+ * Sent by the entering coordinator to tell the exiting coordinator
+ * to hide its shared elements after it has started its shared
+ * element transition. This is temporary until the
+ * interlock of shared elements is figured out.
+ */
+ public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
+
+ /**
+ * Sent by the exiting coordinator (either EnterTransitionCoordinator
+ * or ExitTransitionCoordinator) after the shared elements have
+ * become stationary (shared element transition completes). This tells
+ * the remote coordinator to take control of the shared elements and
+ * that animations may begin. The remote Activity won't start entering
+ * until this message is received, but may wait for
+ * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
+ */
+ public static final int MSG_TAKE_SHARED_ELEMENTS = 103;
+
+ /**
+ * Sent by the exiting coordinator (either
+ * EnterTransitionCoordinator or ExitTransitionCoordinator) after
+ * the exiting Views have finished leaving the scene. This will
+ * be ignored if allowOverlappingTransitions() is true on the
+ * remote coordinator. If it is false, it will trigger the enter
+ * transition to start.
+ */
+ public static final int MSG_EXIT_TRANSITION_COMPLETE = 104;
+
+ /**
+ * Sent by Activity#startActivity to begin the exit transition.
+ */
+ public static final int MSG_START_EXIT_TRANSITION = 105;
+
+ /**
+ * It took too long for a message from the entering Activity, so we canceled the transition.
+ */
+ public static final int MSG_CANCEL = 106;
+
+ /**
+ * When returning, this is the destination location for the shared element.
+ */
+ public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
+
+ /**
+ * Sent by Activity#startActivity to notify the entering activity that enter animation for
+ * back is allowed. If this message is not received, the default exit animation will run when
+ * backing out of an activity (instead of the 'reverse' shared element transition).
+ */
+ public static final int MSG_ALLOW_RETURN_TRANSITION = 108;
+
+ private Window mWindow;
+ final protected ArrayList<String> mAllSharedElementNames;
+ final protected ArrayList<View> mSharedElements = new ArrayList<View>();
+ final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
+ protected ArrayList<View> mTransitioningViews = new ArrayList<View>();
+ protected SharedElementCallback mListener;
+ protected ResultReceiver mResultReceiver;
+ final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
+ final protected boolean mIsReturning;
+ private Runnable mPendingTransition;
+ private boolean mIsStartingTransition;
+ private ArrayList<GhostViewListeners> mGhostViewListeners =
+ new ArrayList<GhostViewListeners>();
+ private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>();
+ private ArrayList<Matrix> mSharedElementParentMatrices;
+ private boolean mSharedElementTransitionComplete;
+ private boolean mViewsTransitionComplete;
+ private boolean mBackgroundAnimatorComplete;
+ private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>();
+
+ public ActivityTransitionCoordinator(Window window,
+ ArrayList<String> allSharedElementNames,
+ SharedElementCallback listener, boolean isReturning) {
+ super(new Handler());
+ mWindow = window;
+ mListener = listener;
+ mAllSharedElementNames = allSharedElementNames;
+ mIsReturning = isReturning;
+ }
+
+ protected void viewsReady(ArrayMap<String, View> sharedElements) {
+ sharedElements.retainAll(mAllSharedElementNames);
+ if (mListener != null) {
+ mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
+ }
+ setSharedElements(sharedElements);
+ if (getViewsTransition() != null && mTransitioningViews != null) {
+ ViewGroup decorView = getDecor();
+ if (decorView != null) {
+ decorView.captureTransitioningViews(mTransitioningViews);
+ }
+ mTransitioningViews.removeAll(mSharedElements);
+ }
+ setEpicenter();
+ }
+
+ /**
+ * Iterates over the shared elements and adds them to the members in order.
+ * Shared elements that are nested in other shared elements are placed after the
+ * elements that they are nested in. This means that layout ordering can be done
+ * from first to last.
+ *
+ * @param sharedElements The map of transition names to shared elements to set into
+ * the member fields.
+ */
+ private void setSharedElements(ArrayMap<String, View> sharedElements) {
+ boolean isFirstRun = true;
+ while (!sharedElements.isEmpty()) {
+ final int numSharedElements = sharedElements.size();
+ for (int i = numSharedElements - 1; i >= 0; i--) {
+ final View view = sharedElements.valueAt(i);
+ final String name = sharedElements.keyAt(i);
+ if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) {
+ sharedElements.removeAt(i);
+ } else if (!isNested(view, sharedElements)) {
+ mSharedElementNames.add(name);
+ mSharedElements.add(view);
+ sharedElements.removeAt(i);
+ }
+ }
+ isFirstRun = false;
+ }
+ }
+
+ /**
+ * Returns true when view is nested in any of the values of sharedElements.
+ */
+ private static boolean isNested(View view, ArrayMap<String, View> sharedElements) {
+ ViewParent parent = view.getParent();
+ boolean isNested = false;
+ while (parent instanceof View) {
+ View parentView = (View) parent;
+ if (sharedElements.containsValue(parentView)) {
+ isNested = true;
+ break;
+ }
+ parent = parentView.getParent();
+ }
+ return isNested;
+ }
+
+ protected void stripOffscreenViews() {
+ if (mTransitioningViews == null) {
+ return;
+ }
+ Rect r = new Rect();
+ for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mTransitioningViews.get(i);
+ if (!view.getGlobalVisibleRect(r)) {
+ mTransitioningViews.remove(i);
+ mStrippedTransitioningViews.add(view);
+ }
+ }
+ }
+
+ protected Window getWindow() {
+ return mWindow;
+ }
+
+ public ViewGroup getDecor() {
+ return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
+ }
+
+ /**
+ * Sets the transition epicenter to the position of the first shared element.
+ */
+ protected void setEpicenter() {
+ View epicenter = null;
+ if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) {
+ int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0));
+ if (index >= 0) {
+ epicenter = mSharedElements.get(index);
+ }
+ }
+ setEpicenter(epicenter);
+ }
+
+ private void setEpicenter(View view) {
+ if (view == null) {
+ mEpicenterCallback.setEpicenter(null);
+ } else {
+ Rect epicenter = new Rect();
+ view.getBoundsOnScreen(epicenter);
+ mEpicenterCallback.setEpicenter(epicenter);
+ }
+ }
+
+ public ArrayList<String> getAcceptedNames() {
+ return mSharedElementNames;
+ }
+
+ public ArrayList<String> getMappedNames() {
+ ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
+ for (int i = 0; i < mSharedElements.size(); i++) {
+ names.add(mSharedElements.get(i).getTransitionName());
+ }
+ return names;
+ }
+
+ public ArrayList<View> copyMappedViews() {
+ return new ArrayList<View>(mSharedElements);
+ }
+
+ protected Transition setTargets(Transition transition, boolean add) {
+ if (transition == null || (add &&
+ (mTransitioningViews == null || mTransitioningViews.isEmpty()))) {
+ return null;
+ }
+ // Add the targets to a set containing transition so that transition
+ // remains unaffected. We don't want to modify the targets of transition itself.
+ TransitionSet set = new TransitionSet();
+ if (mTransitioningViews != null) {
+ for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mTransitioningViews.get(i);
+ if (add) {
+ set.addTarget(view);
+ } else {
+ set.excludeTarget(view, true);
+ }
+ }
+ }
+ if (mStrippedTransitioningViews != null) {
+ for (int i = mStrippedTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mStrippedTransitioningViews.get(i);
+ set.excludeTarget(view, true);
+ }
+ }
+ // By adding the transition after addTarget, we prevent addTarget from
+ // affecting transition.
+ set.addTransition(transition);
+
+ if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
+ // Allow children of excluded transitioning views, but not the views themselves
+ set = new TransitionSet().addTransition(set);
+ }
+
+ return set;
+ }
+
+ protected Transition configureTransition(Transition transition,
+ boolean includeTransitioningViews) {
+ if (transition != null) {
+ transition = transition.clone();
+ transition.setEpicenterCallback(mEpicenterCallback);
+ transition = setTargets(transition, includeTransitioningViews);
+ }
+ noLayoutSuppressionForVisibilityTransitions(transition);
+ return transition;
+ }
+
+ /**
+ * Looks through the transition to see which Views have been included and which have been
+ * excluded. {@code views} will be modified to contain only those Views that are included
+ * in the transition. If {@code transition} is a TransitionSet, it will search through all
+ * contained Transitions to find targeted Views.
+ *
+ * @param transition The transition to look through for inclusion of Views
+ * @param views The list of Views that are to be checked for inclusion. Will be modified
+ * to remove all excluded Views, possibly leaving an empty list.
+ */
+ protected static void removeExcludedViews(Transition transition, ArrayList<View> views) {
+ ArraySet<View> included = new ArraySet<>();
+ findIncludedViews(transition, views, included);
+ views.clear();
+ views.addAll(included);
+ }
+
+ /**
+ * Looks through the transition to see which Views have been included. Only {@code views}
+ * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search
+ * through all contained Transitions to find targeted Views.
+ *
+ * @param transition The transition to look through for inclusion of Views
+ * @param views The list of Views that are to be checked for inclusion.
+ * @param included Modified to contain all Views in views that have at least one Transition
+ * that affects it.
+ */
+ private static void findIncludedViews(Transition transition, ArrayList<View> views,
+ ArraySet<View> included) {
+ if (transition instanceof TransitionSet) {
+ TransitionSet set = (TransitionSet) transition;
+ ArrayList<View> includedViews = new ArrayList<>();
+ final int numViews = views.size();
+ for (int i = 0; i < numViews; i++) {
+ final View view = views.get(i);
+ if (transition.isValidTarget(view)) {
+ includedViews.add(view);
+ }
+ }
+ final int count = set.getTransitionCount();
+ for (int i = 0; i < count; i++) {
+ findIncludedViews(set.getTransitionAt(i), includedViews, included);
+ }
+ } else {
+ final int numViews = views.size();
+ for (int i = 0; i < numViews; i++) {
+ final View view = views.get(i);
+ if (transition.isValidTarget(view)) {
+ included.add(view);
+ }
+ }
+ }
+ }
+
+ protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
+ if (transition1 == null) {
+ return transition2;
+ } else if (transition2 == null) {
+ return transition1;
+ } else {
+ TransitionSet transitionSet = new TransitionSet();
+ transitionSet.addTransition(transition1);
+ transitionSet.addTransition(transition2);
+ return transitionSet;
+ }
+ }
+
+ protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
+ ArrayList<View> localViews) {
+ ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+ if (accepted != null) {
+ for (int i = 0; i < accepted.size(); i++) {
+ sharedElements.put(accepted.get(i), localViews.get(i));
+ }
+ } else {
+ ViewGroup decorView = getDecor();
+ if (decorView != null) {
+ decorView.findNamedViews(sharedElements);
+ }
+ }
+ return sharedElements;
+ }
+
+ protected void setResultReceiver(ResultReceiver resultReceiver) {
+ mResultReceiver = resultReceiver;
+ }
+
+ protected abstract Transition getViewsTransition();
+
+ private void setSharedElementState(View view, String name, Bundle transitionArgs,
+ Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
+ Bundle sharedElementBundle = transitionArgs.getBundle(name);
+ if (sharedElementBundle == null) {
+ return;
+ }
+
+ if (view instanceof ImageView) {
+ int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt >= 0) {
+ ImageView imageView = (ImageView) view;
+ ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
+ imageView.setScaleType(scaleType);
+ if (scaleType == ImageView.ScaleType.MATRIX) {
+ float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
+ tempMatrix.setValues(matrixValues);
+ imageView.setImageMatrix(tempMatrix);
+ }
+ }
+ }
+
+ float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
+ view.setTranslationZ(z);
+ float elevation = sharedElementBundle.getFloat(KEY_ELEVATION);
+ view.setElevation(elevation);
+
+ float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT);
+ float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP);
+ float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT);
+ float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM);
+
+ if (decorLoc != null) {
+ left -= decorLoc[0];
+ top -= decorLoc[1];
+ right -= decorLoc[0];
+ bottom -= decorLoc[1];
+ } else {
+ // Find the location in the view's parent
+ getSharedElementParentMatrix(view, tempMatrix);
+ tempRect.set(left, top, right, bottom);
+ tempMatrix.mapRect(tempRect);
+
+ float leftInParent = tempRect.left;
+ float topInParent = tempRect.top;
+
+ // Find the size of the view
+ view.getInverseMatrix().mapRect(tempRect);
+ float width = tempRect.width();
+ float height = tempRect.height();
+
+ // Now determine the offset due to view transform:
+ view.setLeft(0);
+ view.setTop(0);
+ view.setRight(Math.round(width));
+ view.setBottom(Math.round(height));
+ tempRect.set(0, 0, width, height);
+ view.getMatrix().mapRect(tempRect);
+
+ left = leftInParent - tempRect.left;
+ top = topInParent - tempRect.top;
+ right = left + width;
+ bottom = top + height;
+ }
+
+ int x = Math.round(left);
+ int y = Math.round(top);
+ int width = Math.round(right) - x;
+ int height = Math.round(bottom) - y;
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+
+ view.layout(x, y, x + width, y + height);
+ }
+
+ private void setSharedElementMatrices() {
+ int numSharedElements = mSharedElements.size();
+ if (numSharedElements > 0) {
+ mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
+ }
+ for (int i = 0; i < numSharedElements; i++) {
+ View view = mSharedElements.get(i);
+
+ // Find the location in the view's parent
+ ViewGroup parent = (ViewGroup) view.getParent();
+ Matrix matrix = new Matrix();
+ if (parent != null) {
+ parent.transformMatrixToLocal(matrix);
+ matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
+ }
+ mSharedElementParentMatrices.add(matrix);
+ }
+ }
+
+ private void getSharedElementParentMatrix(View view, Matrix matrix) {
+ final int index = mSharedElementParentMatrices == null ? -1
+ : mSharedElements.indexOf(view);
+ if (index < 0) {
+ matrix.reset();
+ ViewParent viewParent = view.getParent();
+ if (viewParent instanceof ViewGroup) {
+ // Find the location in the view's parent
+ ViewGroup parent = (ViewGroup) viewParent;
+ parent.transformMatrixToLocal(matrix);
+ matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
+ }
+ } else {
+ // The indices of mSharedElementParentMatrices matches the
+ // mSharedElement matrices.
+ Matrix parentMatrix = mSharedElementParentMatrices.get(index);
+ matrix.set(parentMatrix);
+ }
+ }
+
+ protected ArrayList<SharedElementOriginalState> setSharedElementState(
+ Bundle sharedElementState, final ArrayList<View> snapshots) {
+ ArrayList<SharedElementOriginalState> originalImageState =
+ new ArrayList<SharedElementOriginalState>();
+ if (sharedElementState != null) {
+ Matrix tempMatrix = new Matrix();
+ RectF tempRect = new RectF();
+ final int numSharedElements = mSharedElements.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
+ name, sharedElementState);
+ originalImageState.add(originalState);
+ setSharedElementState(sharedElement, name, sharedElementState,
+ tempMatrix, tempRect, null);
+ }
+ }
+ if (mListener != null) {
+ mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
+ }
+ return originalImageState;
+ }
+
+ protected void notifySharedElementEnd(ArrayList<View> snapshots) {
+ if (mListener != null) {
+ mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
+ }
+ }
+
+ protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
+ final View decorView = getDecor();
+ if (decorView != null) {
+ OneShotPreDrawListener.add(decorView, () -> {
+ notifySharedElementEnd(snapshots);
+ });
+ }
+ }
+
+ private static SharedElementOriginalState getOldSharedElementState(View view, String name,
+ Bundle transitionArgs) {
+
+ SharedElementOriginalState state = new SharedElementOriginalState();
+ state.mLeft = view.getLeft();
+ state.mTop = view.getTop();
+ state.mRight = view.getRight();
+ state.mBottom = view.getBottom();
+ state.mMeasuredWidth = view.getMeasuredWidth();
+ state.mMeasuredHeight = view.getMeasuredHeight();
+ state.mTranslationZ = view.getTranslationZ();
+ state.mElevation = view.getElevation();
+ if (!(view instanceof ImageView)) {
+ return state;
+ }
+ Bundle bundle = transitionArgs.getBundle(name);
+ if (bundle == null) {
+ return state;
+ }
+ int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt < 0) {
+ return state;
+ }
+
+ ImageView imageView = (ImageView) view;
+ state.mScaleType = imageView.getScaleType();
+ if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+ state.mMatrix = new Matrix(imageView.getImageMatrix());
+ }
+ return state;
+ }
+
+ protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
+ int numSharedElements = names.size();
+ ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
+ if (numSharedElements == 0) {
+ return snapshots;
+ }
+ Context context = getWindow().getContext();
+ int[] decorLoc = new int[2];
+ ViewGroup decorView = getDecor();
+ if (decorView != null) {
+ decorView.getLocationOnScreen(decorLoc);
+ }
+ Matrix tempMatrix = new Matrix();
+ for (String name: names) {
+ Bundle sharedElementBundle = state.getBundle(name);
+ View snapshot = null;
+ if (sharedElementBundle != null) {
+ Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT);
+ if (parcelable != null && mListener != null) {
+ snapshot = mListener.onCreateSnapshotView(context, parcelable);
+ }
+ if (snapshot != null) {
+ setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc);
+ }
+ }
+ // Even null snapshots are added so they remain in the same order as shared elements.
+ snapshots.add(snapshot);
+ }
+ return snapshots;
+ }
+
+ protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
+ ArrayList<SharedElementOriginalState> originalState) {
+ for (int i = 0; i < originalState.size(); i++) {
+ View view = sharedElements.get(i);
+ SharedElementOriginalState state = originalState.get(i);
+ if (view instanceof ImageView && state.mScaleType != null) {
+ ImageView imageView = (ImageView) view;
+ imageView.setScaleType(state.mScaleType);
+ if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+ imageView.setImageMatrix(state.mMatrix);
+ }
+ }
+ view.setElevation(state.mElevation);
+ view.setTranslationZ(state.mTranslationZ);
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
+ View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
+ View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
+ }
+ }
+
+ protected Bundle captureSharedElementState() {
+ Bundle bundle = new Bundle();
+ RectF tempBounds = new RectF();
+ Matrix tempMatrix = new Matrix();
+ for (int i = 0; i < mSharedElements.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds);
+ }
+ return bundle;
+ }
+
+ protected void clearState() {
+ // Clear the state so that we can't hold any references accidentally and leak memory.
+ mWindow = null;
+ mSharedElements.clear();
+ mTransitioningViews = null;
+ mStrippedTransitioningViews = null;
+ mOriginalAlphas.clear();
+ mResultReceiver = null;
+ mPendingTransition = null;
+ mListener = null;
+ mSharedElementParentMatrices = null;
+ }
+
+ protected long getFadeDuration() {
+ return getWindow().getTransitionBackgroundFadeDuration();
+ }
+
+ protected void hideViews(ArrayList<View> views) {
+ int count = views.size();
+ for (int i = 0; i < count; i++) {
+ View view = views.get(i);
+ if (!mOriginalAlphas.containsKey(view)) {
+ mOriginalAlphas.put(view, view.getAlpha());
+ }
+ view.setAlpha(0f);
+ }
+ }
+
+ protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) {
+ int count = views.size();
+ for (int i = 0; i < count; i++) {
+ showView(views.get(i), setTransitionAlpha);
+ }
+ }
+
+ private void showView(View view, boolean setTransitionAlpha) {
+ Float alpha = mOriginalAlphas.remove(view);
+ if (alpha != null) {
+ view.setAlpha(alpha);
+ }
+ if (setTransitionAlpha) {
+ view.setTransitionAlpha(1f);
+ }
+ }
+
+ /**
+ * Captures placement information for Views with a shared element name for
+ * Activity Transitions.
+ *
+ * @param view The View to capture the placement information for.
+ * @param name The shared element name in the target Activity to apply the placement
+ * information for.
+ * @param transitionArgs Bundle to store shared element placement information.
+ * @param tempBounds A temporary Rect for capturing the current location of views.
+ */
+ protected void captureSharedElementState(View view, String name, Bundle transitionArgs,
+ Matrix tempMatrix, RectF tempBounds) {
+ Bundle sharedElementBundle = new Bundle();
+ tempMatrix.reset();
+ view.transformMatrixToGlobal(tempMatrix);
+ tempBounds.set(0, 0, view.getWidth(), view.getHeight());
+ tempMatrix.mapRect(tempBounds);
+
+ sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left);
+ sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right);
+ sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top);
+ sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom);
+ sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
+ sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation());
+
+ Parcelable bitmap = null;
+ if (mListener != null) {
+ bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds);
+ }
+
+ if (bitmap != null) {
+ sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap);
+ }
+
+ if (view instanceof ImageView) {
+ ImageView imageView = (ImageView) view;
+ int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
+ sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
+ if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
+ float[] matrix = new float[9];
+ imageView.getImageMatrix().getValues(matrix);
+ sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
+ }
+ }
+
+ transitionArgs.putBundle(name, sharedElementBundle);
+ }
+
+
+ protected void startTransition(Runnable runnable) {
+ if (mIsStartingTransition) {
+ mPendingTransition = runnable;
+ } else {
+ mIsStartingTransition = true;
+ runnable.run();
+ }
+ }
+
+ protected void transitionStarted() {
+ mIsStartingTransition = false;
+ }
+
+ /**
+ * Cancels any pending transitions and returns true if there is a transition is in
+ * the middle of starting.
+ */
+ protected boolean cancelPendingTransitions() {
+ mPendingTransition = null;
+ return mIsStartingTransition;
+ }
+
+ protected void moveSharedElementsToOverlay() {
+ if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
+ return;
+ }
+ setSharedElementMatrices();
+ int numSharedElements = mSharedElements.size();
+ ViewGroup decor = getDecor();
+ if (decor != null) {
+ boolean moveWithParent = moveSharedElementWithParent();
+ Matrix tempMatrix = new Matrix();
+ for (int i = 0; i < numSharedElements; i++) {
+ View view = mSharedElements.get(i);
+ if (view.isAttachedToWindow()) {
+ tempMatrix.reset();
+ mSharedElementParentMatrices.get(i).invert(tempMatrix);
+ decor.transformMatrixToLocal(tempMatrix);
+ GhostView.addGhost(view, decor, tempMatrix);
+ ViewGroup parent = (ViewGroup) view.getParent();
+ if (moveWithParent && !isInTransitionGroup(parent, decor)) {
+ GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
+ parent.getViewTreeObserver().addOnPreDrawListener(listener);
+ parent.addOnAttachStateChangeListener(listener);
+ mGhostViewListeners.add(listener);
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean moveSharedElementWithParent() {
+ return true;
+ }
+
+ public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) {
+ if (viewParent == decor || !(viewParent instanceof ViewGroup)) {
+ return false;
+ }
+ ViewGroup parent = (ViewGroup) viewParent;
+ if (parent.isTransitionGroup()) {
+ return true;
+ } else {
+ return isInTransitionGroup(parent.getParent(), decor);
+ }
+ }
+
+ protected void moveSharedElementsFromOverlay() {
+ int numListeners = mGhostViewListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ GhostViewListeners listener = mGhostViewListeners.get(i);
+ listener.removeListener();
+ }
+ mGhostViewListeners.clear();
+
+ if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
+ return;
+ }
+ ViewGroup decor = getDecor();
+ if (decor != null) {
+ ViewGroupOverlay overlay = decor.getOverlay();
+ int count = mSharedElements.size();
+ for (int i = 0; i < count; i++) {
+ View sharedElement = mSharedElements.get(i);
+ GhostView.removeGhost(sharedElement);
+ }
+ }
+ }
+
+ protected void setGhostVisibility(int visibility) {
+ int numSharedElements = mSharedElements.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ GhostView ghostView = GhostView.getGhost(mSharedElements.get(i));
+ if (ghostView != null) {
+ ghostView.setVisibility(visibility);
+ }
+ }
+ }
+
+ protected void scheduleGhostVisibilityChange(final int visibility) {
+ final View decorView = getDecor();
+ if (decorView != null) {
+ OneShotPreDrawListener.add(decorView, () -> {
+ setGhostVisibility(visibility);
+ });
+ }
+ }
+
+ protected boolean isViewsTransitionComplete() {
+ return mViewsTransitionComplete;
+ }
+
+ protected void viewsTransitionComplete() {
+ mViewsTransitionComplete = true;
+ startInputWhenTransitionsComplete();
+ }
+
+ protected void backgroundAnimatorComplete() {
+ mBackgroundAnimatorComplete = true;
+ }
+
+ protected void sharedElementTransitionComplete() {
+ mSharedElementTransitionComplete = true;
+ startInputWhenTransitionsComplete();
+ }
+ private void startInputWhenTransitionsComplete() {
+ if (mViewsTransitionComplete && mSharedElementTransitionComplete) {
+ final View decor = getDecor();
+ if (decor != null) {
+ final ViewRootImpl viewRoot = decor.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.setPausedForTransition(false);
+ }
+ }
+ onTransitionsComplete();
+ }
+ }
+
+ protected void pauseInput() {
+ final View decor = getDecor();
+ final ViewRootImpl viewRoot = decor == null ? null : decor.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.setPausedForTransition(true);
+ }
+ }
+
+ protected void onTransitionsComplete() {}
+
+ protected class ContinueTransitionListener extends TransitionListenerAdapter {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ mIsStartingTransition = false;
+ Runnable pending = mPendingTransition;
+ mPendingTransition = null;
+ if (pending != null) {
+ startTransition(pending);
+ }
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ }
+ }
+
+ private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
+ for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
+ if (scaleType == SCALE_TYPE_VALUES[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected void setTransitioningViewsVisiblity(int visiblity, boolean invalidate) {
+ final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size();
+ for (int i = 0; i < numElements; i++) {
+ final View view = mTransitioningViews.get(i);
+ if (invalidate) {
+ // Allow the view to be invalidated by the visibility change
+ view.setVisibility(visiblity);
+ } else {
+ // Don't invalidate the view with the visibility change
+ view.setTransitionVisibility(visiblity);
+ }
+ }
+ }
+
+ /**
+ * Blocks suppressLayout from Visibility transitions. It is ok to suppress the layout,
+ * but we don't want to force the layout when suppressLayout becomes false. This leads
+ * to visual glitches.
+ */
+ private static void noLayoutSuppressionForVisibilityTransitions(Transition transition) {
+ if (transition instanceof Visibility) {
+ final Visibility visibility = (Visibility) transition;
+ visibility.setSuppressLayout(false);
+ } else if (transition instanceof TransitionSet) {
+ final TransitionSet set = (TransitionSet) transition;
+ final int count = set.getTransitionCount();
+ for (int i = 0; i < count; i++) {
+ noLayoutSuppressionForVisibilityTransitions(set.getTransitionAt(i));
+ }
+ }
+ }
+
+ public boolean isTransitionRunning() {
+ return !(mViewsTransitionComplete && mSharedElementTransitionComplete &&
+ mBackgroundAnimatorComplete);
+ }
+
+ private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
+ private Rect mEpicenter;
+
+ public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
+
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return mEpicenter;
+ }
+ }
+
+ private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener,
+ View.OnAttachStateChangeListener {
+ private View mView;
+ private ViewGroup mDecor;
+ private View mParent;
+ private Matrix mMatrix = new Matrix();
+ private ViewTreeObserver mViewTreeObserver;
+
+ public GhostViewListeners(View view, View parent, ViewGroup decor) {
+ mView = view;
+ mParent = parent;
+ mDecor = decor;
+ mViewTreeObserver = parent.getViewTreeObserver();
+ }
+
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ GhostView ghostView = GhostView.getGhost(mView);
+ if (ghostView == null || !mView.isAttachedToWindow()) {
+ removeListener();
+ } else {
+ GhostView.calculateMatrix(mView, mDecor, mMatrix);
+ ghostView.setMatrix(mMatrix);
+ }
+ return true;
+ }
+
+ public void removeListener() {
+ if (mViewTreeObserver.isAlive()) {
+ mViewTreeObserver.removeOnPreDrawListener(this);
+ } else {
+ mParent.getViewTreeObserver().removeOnPreDrawListener(this);
+ }
+ mParent.removeOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mViewTreeObserver = v.getViewTreeObserver();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ removeListener();
+ }
+ }
+
+ static class SharedElementOriginalState {
+ int mLeft;
+ int mTop;
+ int mRight;
+ int mBottom;
+ int mMeasuredWidth;
+ int mMeasuredHeight;
+ ImageView.ScaleType mScaleType;
+ Matrix mMatrix;
+ float mTranslationZ;
+ float mElevation;
+ }
+}
diff --git a/android-34/android/app/ActivityTransitionState.java b/android-34/android/app/ActivityTransitionState.java
new file mode 100644
index 0000000..6f4bb45
--- /dev/null
+++ b/android-34/android/app/ActivityTransitionState.java
@@ -0,0 +1,405 @@
+/*
+ * 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.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import com.android.internal.view.OneShotPreDrawListener;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This class contains all persistence-related functionality for Activity Transitions.
+ * Activities start exit and enter Activity Transitions through this class.
+ */
+class ActivityTransitionState {
+
+ private static final String PENDING_EXIT_SHARED_ELEMENTS = "android:pendingExitSharedElements";
+
+ private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
+
+ private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
+
+ /**
+ * The shared elements that the calling Activity has said that they transferred to this
+ * Activity and will be transferred back during exit animation.
+ */
+ private ArrayList<String> mPendingExitNames;
+
+ /**
+ * The names of shared elements that were shared to the called Activity.
+ */
+ private ArrayList<String> mExitingFrom;
+
+ /**
+ * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
+ */
+ private ArrayList<String> mExitingTo;
+
+ /**
+ * The local Views that were shared out, mapped to those elements in mExitingFrom.
+ */
+ private ArrayList<View> mExitingToView;
+
+ /**
+ * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
+ * Visibility of exited Views.
+ */
+ private ExitTransitionCoordinator mCalledExitCoordinator;
+
+ /**
+ * The ExitTransitionCoordinator used to return to a previous Activity when called with
+ * {@link android.app.Activity#finishAfterTransition()}.
+ */
+ private ExitTransitionCoordinator mReturnExitCoordinator;
+
+ /**
+ * We must be able to cancel entering transitions to stop changing the Window to
+ * opaque when we exit before making the Window opaque.
+ */
+ private EnterTransitionCoordinator mEnterTransitionCoordinator;
+
+ /**
+ * ActivityOptions used on entering this Activity.
+ */
+ private ActivityOptions mEnterActivityOptions;
+
+ /**
+ * Has an exit transition been started? If so, we don't want to double-exit.
+ */
+ private boolean mHasExited;
+
+ /**
+ * Postpone painting and starting the enter transition until this is false.
+ */
+ private boolean mIsEnterPostponed;
+
+ /**
+ * Potential exit transition coordinators.
+ */
+ private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
+
+ /**
+ * Next key for mExitTransitionCoordinator.
+ */
+ private int mExitTransitionCoordinatorsKey = 1;
+
+ private boolean mIsEnterTriggered;
+
+ public ActivityTransitionState() {
+ }
+
+ public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
+ if (mExitTransitionCoordinators == null) {
+ mExitTransitionCoordinators = new SparseArray<>();
+ }
+ WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
+ // clean up old references:
+ for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
+ WeakReference<ExitTransitionCoordinator> oldRef
+ = mExitTransitionCoordinators.valueAt(i);
+ if (oldRef.refersTo(null)) {
+ mExitTransitionCoordinators.removeAt(i);
+ }
+ }
+ int newKey = mExitTransitionCoordinatorsKey++;
+ mExitTransitionCoordinators.append(newKey, ref);
+ return newKey;
+ }
+
+ public void readState(Bundle bundle) {
+ if (bundle != null) {
+ if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
+ mPendingExitNames = bundle.getStringArrayList(PENDING_EXIT_SHARED_ELEMENTS);
+ }
+ if (mEnterTransitionCoordinator == null) {
+ mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
+ mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
+ }
+ }
+ }
+
+ /**
+ * Returns the element names to be used for exit animation. It caches the list internally so
+ * that it is preserved through activty destroy and restore.
+ */
+ private ArrayList<String> getPendingExitNames() {
+ if (mPendingExitNames == null
+ && mEnterTransitionCoordinator != null
+ && !mEnterTransitionCoordinator.isReturning()
+ ) {
+ mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
+ }
+ return mPendingExitNames;
+ }
+
+ public void saveState(Bundle bundle) {
+ ArrayList<String> pendingExitNames = getPendingExitNames();
+ if (pendingExitNames != null) {
+ bundle.putStringArrayList(PENDING_EXIT_SHARED_ELEMENTS, pendingExitNames);
+ }
+ if (mExitingFrom != null) {
+ bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
+ bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
+ }
+ }
+
+ public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+ final Window window = activity.getWindow();
+ if (window == null) {
+ return;
+ }
+ // ensure Decor View has been created so that the window features are activated
+ window.getDecorView();
+ if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+ && options != null && mEnterActivityOptions == null
+ && mEnterTransitionCoordinator == null
+ && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mEnterActivityOptions = options;
+ mIsEnterTriggered = false;
+ if (mEnterActivityOptions.isReturning()) {
+ restoreExitedViews();
+ int result = mEnterActivityOptions.getResultCode();
+ if (result != 0) {
+ Intent intent = mEnterActivityOptions.getResultData();
+ if (intent != null) {
+ intent.setExtrasClassLoader(activity.getClassLoader());
+ }
+ activity.onActivityReenter(result, intent);
+ }
+ }
+ }
+ }
+
+ public void enterReady(Activity activity) {
+ if (mEnterActivityOptions == null || mIsEnterTriggered) {
+ return;
+ }
+ mIsEnterTriggered = true;
+ mHasExited = false;
+ ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
+ ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
+ final boolean isReturning = mEnterActivityOptions.isReturning();
+ if (isReturning) {
+ restoreExitedViews();
+ activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
+ }
+ getPendingExitNames(); // Set mPendingExitNames before resetting mEnterTransitionCoordinator
+ mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
+ resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
+ mEnterActivityOptions.isCrossTask());
+ if (mEnterActivityOptions.isCrossTask()) {
+ mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+ mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+ }
+
+ if (!mIsEnterPostponed) {
+ startEnter();
+ }
+ }
+
+ public void postponeEnterTransition() {
+ mIsEnterPostponed = true;
+ }
+
+ public void startPostponedEnterTransition() {
+ if (mIsEnterPostponed) {
+ mIsEnterPostponed = false;
+ if (mEnterTransitionCoordinator != null) {
+ startEnter();
+ }
+ }
+ }
+
+ private void startEnter() {
+ if (mEnterTransitionCoordinator.isReturning()) {
+ if (mExitingToView != null) {
+ mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
+ mExitingToView);
+ } else {
+ mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
+ }
+ } else {
+ mEnterTransitionCoordinator.namedViewsReady(null, null);
+ mPendingExitNames = null;
+ }
+
+ mExitingFrom = null;
+ mExitingTo = null;
+ mExitingToView = null;
+ mEnterActivityOptions = null;
+ }
+
+ public void onStop(Activity activity) {
+ restoreExitedViews();
+ if (mEnterTransitionCoordinator != null) {
+ getPendingExitNames(); // Set mPendingExitNames before clearing
+ mEnterTransitionCoordinator.stop();
+ mEnterTransitionCoordinator = null;
+ }
+ if (mReturnExitCoordinator != null) {
+ mReturnExitCoordinator.stop(activity);
+ mReturnExitCoordinator = null;
+ }
+ }
+
+ public void onResume(Activity activity) {
+ // After orientation change, the onResume can come in before the top Activity has
+ // left, so if the Activity is not top, wait a second for the top Activity to exit.
+ if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
+ restoreExitedViews();
+ restoreReenteringViews();
+ } else {
+ activity.mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mEnterTransitionCoordinator == null ||
+ mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
+ restoreExitedViews();
+ restoreReenteringViews();
+ } else if (mEnterTransitionCoordinator.isReturning()) {
+ mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> {
+ getPendingExitNames(); // Set mPendingExitNames before clearing
+ mEnterTransitionCoordinator = null;
+ });
+ }
+ }
+ }, 1000);
+ }
+ }
+
+ public void clear() {
+ mPendingExitNames = null;
+ mExitingFrom = null;
+ mExitingTo = null;
+ mExitingToView = null;
+ mCalledExitCoordinator = null;
+ mEnterTransitionCoordinator = null;
+ mEnterActivityOptions = null;
+ mExitTransitionCoordinators = null;
+ }
+
+ private void restoreExitedViews() {
+ if (mCalledExitCoordinator != null) {
+ mCalledExitCoordinator.resetViews();
+ mCalledExitCoordinator = null;
+ }
+ }
+
+ private void restoreReenteringViews() {
+ if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
+ !mEnterTransitionCoordinator.isCrossTask()) {
+ mEnterTransitionCoordinator.forceViewsToAppear();
+ mExitingFrom = null;
+ mExitingTo = null;
+ mExitingToView = null;
+ }
+ }
+
+ public boolean startExitBackTransition(final Activity activity) {
+ ArrayList<String> pendingExitNames = getPendingExitNames();
+ if (pendingExitNames == null || mCalledExitCoordinator != null) {
+ return false;
+ } else {
+ if (!mHasExited) {
+ mHasExited = true;
+ Transition enterViewsTransition = null;
+ ViewGroup decor = null;
+ boolean delayExitBack = false;
+ if (mEnterTransitionCoordinator != null) {
+ enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
+ decor = mEnterTransitionCoordinator.getDecor();
+ delayExitBack = mEnterTransitionCoordinator.cancelEnter();
+ mEnterTransitionCoordinator = null;
+ if (enterViewsTransition != null && decor != null) {
+ enterViewsTransition.pause(decor);
+ }
+ }
+
+ mReturnExitCoordinator = new ExitTransitionCoordinator(
+ new ExitTransitionCoordinator.ActivityExitTransitionCallbacks(activity),
+ activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames,
+ null, null, true);
+ if (enterViewsTransition != null && decor != null) {
+ enterViewsTransition.resume(decor);
+ }
+ if (delayExitBack && decor != null) {
+ final ViewGroup finalDecor = decor;
+ OneShotPreDrawListener.add(decor, () -> {
+ if (mReturnExitCoordinator != null) {
+ mReturnExitCoordinator.startExit(activity);
+ }
+ });
+ } else {
+ mReturnExitCoordinator.startExit(activity);
+ }
+ }
+ return true;
+ }
+ }
+
+ public boolean isTransitionRunning() {
+ // Note that *only* enter *or* exit will be running at any given time
+ if (mEnterTransitionCoordinator != null) {
+ if (mEnterTransitionCoordinator.isTransitionRunning()) {
+ return true;
+ }
+ }
+ if (mCalledExitCoordinator != null) {
+ if (mCalledExitCoordinator.isTransitionRunning()) {
+ return true;
+ }
+ }
+ if (mReturnExitCoordinator != null) {
+ if (mReturnExitCoordinator.isTransitionRunning()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void startExitOutTransition(Activity activity, Bundle options) {
+ getPendingExitNames(); // Set mPendingExitNames before clearing mEnterTransitionCoordinator
+ mEnterTransitionCoordinator = null;
+ if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
+ mExitTransitionCoordinators == null) {
+ return;
+ }
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ int key = activityOptions.getExitCoordinatorKey();
+ int index = mExitTransitionCoordinators.indexOfKey(key);
+ if (index >= 0) {
+ mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
+ mExitTransitionCoordinators.removeAt(index);
+ if (mCalledExitCoordinator != null) {
+ mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
+ mExitingTo = mCalledExitCoordinator.getMappedNames();
+ mExitingToView = mCalledExitCoordinator.copyMappedViews();
+ mCalledExitCoordinator.startExit();
+ }
+ }
+ }
+ }
+}
diff --git a/android-34/android/app/AlarmManager.java b/android-34/android/app/AlarmManager.java
new file mode 100644
index 0000000..ec6a8b8
--- /dev/null
+++ b/android-34/android/app/AlarmManager.java
@@ -0,0 +1,1658 @@
+/*
+ * 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.app;
+
+import android.Manifest;
+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.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides access to the system alarm services. These allow you
+ * to schedule your application to be run at some point in the future. When
+ * an alarm goes off, the {@link Intent} that had been registered for it
+ * is broadcast by the system, automatically starting the target application
+ * if it is not already running. Registered alarms are retained while the
+ * device is asleep (and can optionally wake the device up if they go off
+ * during that time), but will be cleared if it is turned off and rebooted.
+ *
+ * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
+ * onReceive() method is executing. This guarantees that the phone will not sleep
+ * until you have finished handling the broadcast. Once onReceive() returns, the
+ * Alarm Manager releases this wake lock. This means that the phone will in some
+ * cases sleep as soon as your onReceive() method completes. If your alarm receiver
+ * called {@link android.content.Context#startService Context.startService()}, it
+ * is possible that the phone will sleep before the requested service is launched.
+ * To prevent this, your BroadcastReceiver and Service will need to implement a
+ * separate wake lock policy to ensure that the phone continues running until the
+ * service becomes available.
+ *
+ * <p><b>Note: The Alarm Manager is intended for cases where you want to have
+ * your application code run at a specific time, even if your application is
+ * not currently running. For normal timing operations (ticks, timeouts,
+ * etc) it is easier and much more efficient to use
+ * {@link android.os.Handler}.</b>
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 19
+ * ({@link android.os.Build.VERSION_CODES#KITKAT}) alarm delivery is inexact:
+ * the OS will shift alarms in order to minimize wakeups and battery use. There are
+ * new APIs to support applications which need strict delivery guarantees; see
+ * {@link #setWindow(int, long, long, PendingIntent)} and
+ * {@link #setExact(int, long, PendingIntent)}. Applications whose {@code targetSdkVersion}
+ * is earlier than API 19 will continue to see the previous behavior in which all
+ * alarms are delivered exactly when requested.
+ */
+@SystemService(Context.ALARM_SERVICE)
+public class AlarmManager {
+ private static final String TAG = "AlarmManager";
+
+ /**
+ * Prefix used by {{@link #makeTag(long, WorkSource)}} to make a tag on behalf of the caller
+ * when the {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API is
+ * used. This prefix is a unique sequence of characters to differentiate with other tags that
+ * apps may provide to other APIs that accept a listener callback.
+ */
+ private static final String GENERATED_TAG_PREFIX = "$android.alarm.generated";
+
+ /** @hide */
+ @IntDef(prefix = { "RTC", "ELAPSED" }, value = {
+ RTC_WAKEUP,
+ RTC,
+ ELAPSED_REALTIME_WAKEUP,
+ ELAPSED_REALTIME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AlarmType {}
+
+ /**
+ * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
+ * (wall clock time in UTC), which will wake up the device when
+ * it goes off.
+ */
+ public static final int RTC_WAKEUP = 0;
+ /**
+ * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
+ * (wall clock time in UTC). This alarm does not wake the
+ * device up; if it goes off while the device is asleep, it will not be
+ * delivered until the next time the device wakes up.
+ */
+ public static final int RTC = 1;
+ /**
+ * Alarm time in {@link android.os.SystemClock#elapsedRealtime
+ * SystemClock.elapsedRealtime()} (time since boot, including sleep),
+ * which will wake up the device when it goes off.
+ */
+ public static final int ELAPSED_REALTIME_WAKEUP = 2;
+ /**
+ * Alarm time in {@link android.os.SystemClock#elapsedRealtime
+ * SystemClock.elapsedRealtime()} (time since boot, including sleep).
+ * This alarm does not wake the device up; if it goes off while the device
+ * is asleep, it will not be delivered until the next time the device
+ * wakes up.
+ */
+ public static final int ELAPSED_REALTIME = 3;
+
+ /**
+ * Broadcast Action: Sent after the value returned by
+ * {@link #getNextAlarmClock()} has changed.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * It is only sent to registered receivers.</p>
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED =
+ "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
+
+ /**
+ * Broadcast Action: An app is granted the
+ * {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} permission.
+ *
+ * <p>When the user revokes the {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+ * permission, all alarms scheduled with
+ * {@link #setExact}, {@link #setExactAndAllowWhileIdle} and
+ * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will be deleted.
+ *
+ * <p>When the user grants the {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM},
+ * this broadcast will be sent. Applications can reschedule all the necessary alarms when
+ * receiving it.
+ *
+ * <p>This broadcast will <em>not</em> be sent when the user revokes the permission.
+ *
+ * <p><em>Note:</em>
+ * Applications are still required to check {@link #canScheduleExactAlarms()}
+ * before using the above APIs after receiving this broadcast,
+ * because it's possible that the permission is already revoked again by the time
+ * applications receive this broadcast.
+ *
+ * <p>This broadcast will be sent to both runtime receivers and manifest receivers.
+ *
+ * <p>This broadcast is sent as a foreground broadcast.
+ * See {@link android.content.Intent#FLAG_RECEIVER_FOREGROUND}.
+ *
+ * <p>When an application receives this broadcast, it's allowed to start a foreground service.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED =
+ "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static final long WINDOW_EXACT = 0;
+ /** @hide */
+ @UnsupportedAppUsage
+ public static final long WINDOW_HEURISTIC = -1;
+
+ /**
+ * Flag for alarms: this is to be a stand-alone alarm, that should not be batched with
+ * other alarms.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int FLAG_STANDALONE = 1<<0;
+
+ /**
+ * Flag for alarms: this alarm would like to wake the device even if it is idle. This
+ * is, for example, an alarm for an alarm clock.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int FLAG_WAKE_FROM_IDLE = 1<<1;
+
+ /**
+ * Flag for alarms: this alarm would like to still execute even if the device is
+ * idle. This won't bring the device out of idle, just allow this specific alarm to
+ * run. Note that this means the actual time this alarm goes off can be inconsistent
+ * with the time of non-allow-while-idle alarms (it could go earlier than the time
+ * requested by another alarm).
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
+
+ /**
+ * Flag for alarms: same as {@link #FLAG_ALLOW_WHILE_IDLE}, but doesn't have restrictions
+ * on how frequently it can be scheduled. Only available (and automatically applied) to
+ * system alarms.
+ *
+ * <p>Note that alarms set with a {@link WorkSource} <b>do not</b> get this flag.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED = 1<<3;
+
+ /**
+ * Flag for alarms: this alarm marks the point where we would like to come out of idle
+ * mode. It may be moved by the alarm manager to match the first wake-from-idle alarm.
+ * Scheduling an alarm with this flag puts the alarm manager in to idle mode, where it
+ * avoids scheduling any further alarms until the marker alarm is executed.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int FLAG_IDLE_UNTIL = 1<<4;
+
+ /**
+ * Flag for alarms: Used to provide backwards compatibility for apps with targetSdkVersion less
+ * than {@link Build.VERSION_CODES#S}
+ * @hide
+ */
+ public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5;
+
+ /**
+ * Flag for alarms: Used to mark prioritized alarms. These alarms will get to execute while idle
+ * and can be sent separately from other alarms that may be already due at the time.
+ * These alarms can be set via
+ * {@link #setPrioritized(int, long, long, String, Executor, OnAlarmListener)}
+ * @hide
+ */
+ public static final int FLAG_PRIORITIZE = 1 << 6;
+
+ /**
+ * For apps targeting {@link Build.VERSION_CODES#S} or above, any APIs setting exact alarms,
+ * e.g. {@link #setExact(int, long, PendingIntent)},
+ * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} and others will require holding a new
+ * permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM}
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
+
+ /**
+ * For apps targeting {@link Build.VERSION_CODES#S} or above, all inexact alarms will require
+ * to have a minimum window size, expected to be on the order of a few minutes.
+ *
+ * Practically, any alarms requiring smaller windows are the same as exact alarms and should use
+ * the corresponding APIs provided, like {@link #setExact(int, long, PendingIntent)}, et al.
+ *
+ * Inexact alarm with shorter windows specified will have their windows elongated by the system.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ public static final long ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS = 185199076L;
+
+ /**
+ * For apps targeting {@link Build.VERSION_CODES#TIRAMISU} or above, certain kinds of apps can
+ * use {@link Manifest.permission#USE_EXACT_ALARM} to schedule exact alarms.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long ENABLE_USE_EXACT_ALARM = 218533173L;
+
+ /**
+ * The permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the
+ * user explicitly allows it from Settings.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L;
+
+ /**
+ * Holding the permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will no longer pin
+ * the standby-bucket of the app to
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET} or better.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long SCHEDULE_EXACT_ALARM_DOES_NOT_ELEVATE_BUCKET = 262645982L;
+
+ /**
+ * Exact alarms expecting a {@link OnAlarmListener} callback will be dropped when the calling
+ * app goes into cached state.
+ *
+ * @hide
+ */
+ @ChangeId
+ public static final long EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED = 265195908L;
+
+ @UnsupportedAppUsage
+ private final IAlarmManager mService;
+ private final Context mContext;
+ private final String mPackageName;
+ private final boolean mAlwaysExact;
+ private final int mTargetSdkVersion;
+ private final Handler mMainThreadHandler;
+
+ /**
+ * Direct-notification alarms: the requester must be running continuously from the
+ * time the alarm is set to the time it is delivered, or delivery will fail. Only
+ * one-shot alarms can be set using this mechanism, not repeating alarms.
+ */
+ public interface OnAlarmListener {
+ /**
+ * Callback method that is invoked by the system when the alarm time is reached.
+ */
+ void onAlarm();
+ }
+
+ final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
+ final OnAlarmListener mListener;
+ Executor mExecutor;
+ IAlarmCompleteListener mCompletion;
+
+ public ListenerWrapper(OnAlarmListener listener) {
+ mListener = listener;
+ }
+
+ void setExecutor(Executor e) {
+ mExecutor = e;
+ }
+
+ public void cancel() {
+ try {
+ mService.remove(null, this);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void doAlarm(IAlarmCompleteListener alarmManager) {
+ mCompletion = alarmManager;
+
+ mExecutor.execute(this);
+ }
+
+ @Override
+ public void run() {
+ // Now deliver it to the app
+ try {
+ mListener.onAlarm();
+ } finally {
+ // No catch -- make sure to report completion to the system process,
+ // but continue to allow the exception to crash the app.
+
+ try {
+ mCompletion.alarmComplete(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to report completion to Alarm Manager!", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Tracking of the OnAlarmListener -> ListenerWrapper mapping, for cancel() support.
+ * An entry is guaranteed to stay in this map as long as its ListenerWrapper is held by the
+ * server.
+ *
+ * <p>Access is synchronized on the AlarmManager class object.
+ */
+ private static WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>> sWrappers;
+
+ /**
+ * package private on purpose
+ */
+ AlarmManager(IAlarmManager service, Context ctx) {
+ mService = service;
+
+ mContext = ctx;
+ mPackageName = ctx.getPackageName();
+ mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
+ mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
+ mMainThreadHandler = new Handler(ctx.getMainLooper());
+ }
+
+ private long legacyExactLength() {
+ return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);
+ }
+
+ /**
+ * <p>Schedule an alarm. <b>Note: for timing operations (ticks, timeouts,
+ * etc) it is easier and much more efficient to use {@link android.os.Handler}.</b>
+ * If there is already an alarm scheduled for the same IntentSender, that previous
+ * alarm will first be canceled.
+ *
+ * <p>If the stated trigger time is in the past, the alarm will be triggered
+ * immediately. If there is already an alarm for this Intent
+ * scheduled (with the equality of two intents being defined by
+ * {@link Intent#filterEquals}), then it will be removed and replaced by
+ * this one.
+ *
+ * <p>
+ * The alarm is an Intent broadcast that goes to a broadcast receiver that
+ * you registered with {@link android.content.Context#registerReceiver}
+ * or through the <receiver> tag in an AndroidManifest.xml file.
+ *
+ * <p>
+ * Alarm intents are delivered with a data extra of type int called
+ * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
+ * how many past alarm events have been accumulated into this intent
+ * broadcast. Recurring alarms that have gone undelivered because the
+ * phone was asleep may have a count greater than one when delivered.
+ *
+ * <div class="note">
+ * <p>
+ * <b>Note:</b> Beginning in API 19, the trigger time passed to this method
+ * is treated as inexact: the alarm will not be delivered before this time, but
+ * may be deferred and delivered some time later. The OS will use
+ * this policy in order to "batch" alarms together across the entire system,
+ * minimizing the number of times the device needs to "wake up" and minimizing
+ * battery use. In general, alarms scheduled in the near future will not
+ * be deferred as long as alarms scheduled far in the future.
+ *
+ * <p>
+ * With the new batching policy, delivery ordering guarantees are not as
+ * strong as they were previously. If the application sets multiple alarms,
+ * it is possible that these alarms' <em>actual</em> delivery ordering may not match
+ * the order of their <em>requested</em> delivery times. If your application has
+ * strong ordering requirements there are other APIs that you can use to get
+ * the necessary behavior; see {@link #setWindow(int, long, long, PendingIntent)}
+ * and {@link #setExact(int, long, PendingIntent)}.
+ *
+ * <p>
+ * Applications whose {@code targetSdkVersion} is before API 19 will
+ * continue to get the previous alarm behavior: all of their scheduled alarms
+ * will be treated as exact.
+ * </div>
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see android.os.Handler
+ * @see #setExact
+ * @see #setRepeating
+ * @see #setWindow
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ */
+ public void set(@AlarmType int type, long triggerAtMillis, @NonNull PendingIntent operation) {
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
+ (Handler) null, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, PendingIntent)}. Rather than
+ * supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param tag string describing the alarm, used for logging and battery-use
+ * attribution
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * called when the alarm time is reached. A given OnAlarmListener instance can
+ * only be the target of a single pending alarm, just as a given PendingIntent
+ * can only be used with one alarm at a time.
+ * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
+ * callback, or {@code null} to run that callback on the main looper.
+ */
+ public void set(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
+ targetHandler, null, null);
+ }
+
+ /**
+ * Schedule a repeating alarm. <b>Note: for timing operations (ticks,
+ * timeouts, etc) it is easier and much more efficient to use
+ * {@link android.os.Handler}.</b> If there is already an alarm scheduled
+ * for the same IntentSender, it will first be canceled.
+ *
+ * <p>Like {@link #set}, except you can also supply a period at which
+ * the alarm will automatically repeat. This alarm continues
+ * repeating until explicitly removed with {@link #cancel}. If the stated
+ * trigger time is in the past, the alarm will be triggered immediately, with an
+ * alarm count depending on how far in the past the trigger time is relative
+ * to the repeat interval.
+ *
+ * <p>If an alarm is delayed (by system sleep, for example, for non
+ * _WAKEUP alarm types), a skipped repeat will be delivered as soon as
+ * possible. After that, future alarms will be delivered according to the
+ * original schedule; they do not drift over time. For example, if you have
+ * set a recurring alarm for the top of every hour but the phone was asleep
+ * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
+ * then the next alarm will be sent at 9:00.
+ *
+ * <p>If your application wants to allow the delivery times to drift in
+ * order to guarantee that at least a certain time interval always elapses
+ * between alarms, then the approach to take is to use one-time alarms,
+ * scheduling the next one yourself when handling each alarm delivery.
+ *
+ * <p class="note">
+ * <b>Note:</b> as of API 19, all repeating alarms are inexact. If your
+ * application needs precise delivery times then it must use one-time
+ * exact alarms, rescheduling each time as described above. Legacy applications
+ * whose {@code targetSdkVersion} is earlier than API 19 will continue to have all
+ * of their alarms, including repeating alarms, treated as exact.
+ * <p>Apps targeting {@link Build.VERSION_CODES#S} will need to set the flag
+ * {@link PendingIntent#FLAG_MUTABLE} on the {@link PendingIntent} being used to set this alarm,
+ * if they want the alarm count to be supplied with the key {@link Intent#EXTRA_ALARM_COUNT}.
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should first
+ * go off, using the appropriate clock (depending on the alarm type).
+ * @param intervalMillis interval in milliseconds between subsequent repeats
+ * of the alarm.
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see android.os.Handler
+ * @see #set
+ * @see #setExact
+ * @see #setWindow
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ * @see Intent#EXTRA_ALARM_COUNT
+ */
+ public void setRepeating(@AlarmType int type, long triggerAtMillis,
+ long intervalMillis, @NonNull PendingIntent operation) {
+ setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
+ null, null, (Handler) null, null, null);
+ }
+
+ /**
+ * Schedule an alarm to be delivered within a given window of time. This method
+ * is similar to {@link #set(int, long, PendingIntent)}, but allows the
+ * application to precisely control the degree to which its delivery might be
+ * adjusted by the OS. This method allows an application to take advantage of the
+ * battery optimizations that arise from delivery batching even when it has
+ * modest timeliness requirements for its alarms.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+ *
+ * <p>
+ * This method can also be used to achieve strict ordering guarantees among
+ * multiple alarms by ensuring that the windows requested for each alarm do
+ * not intersect.
+ *
+ * <p>
+ * When precise delivery is not required, applications should use the standard
+ * {@link #set(int, long, PendingIntent)} method. This will give the OS the most
+ * flexibility to minimize wakeups and battery use. For alarms that must be delivered
+ * at precisely-specified times with no acceptable variation, applications can use
+ * {@link #setExact(int, long, PendingIntent)}.
+ *
+ * @param type type of alarm.
+ * @param windowStartMillis The earliest time, in milliseconds, that the alarm should
+ * be delivered, expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param windowLengthMillis The length of the requested delivery window,
+ * in milliseconds. The alarm will be delivered no later than this many
+ * milliseconds after {@code windowStartMillis}. Note that this parameter
+ * is a <i>duration,</i> not the timestamp of the end of the window.
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see #set
+ * @see #setExact
+ * @see #setRepeating
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ */
+ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @NonNull PendingIntent operation) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
+ null, null, (Handler) null, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+ *
+ * @see #setWindow(int, long, long, PendingIntent)
+ */
+ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @Nullable String tag, @NonNull OnAlarmListener listener,
+ @Nullable Handler targetHandler) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ targetHandler, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Executor.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+ *
+ * @see #setWindow(int, long, long, PendingIntent)
+ */
+ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @Nullable String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ executor, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Executor.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+ *
+ * @see #setWindow(int, long, long, PendingIntent)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource,
+ @NonNull OnAlarmListener listener) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ executor, workSource, null);
+ }
+
+ /**
+ * Schedule an alarm that is prioritized by the system while the device is in power saving modes
+ * such as battery saver and device idle (doze).
+ *
+ * <p>
+ * Apps that use this are not guaranteed to get all alarms as requested during power saving
+ * modes, i.e. the system may still impose restrictions on how frequently these alarms will go
+ * off for a particular application, like requiring a certain minimum duration be elapsed
+ * between consecutive alarms. This duration will be normally be in the order of a few minutes.
+ *
+ * <p>
+ * When the system wakes up to deliver these alarms, it may not deliver any of the other pending
+ * alarms set earlier by the calling app, even the special ones set via
+ * {@link #setAndAllowWhileIdle(int, long, PendingIntent)} or
+ * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)}. So the caller should not
+ * expect these to arrive in any relative order to its other alarms.
+ *
+ * @param type type of alarm
+ * @param windowStartMillis The earliest time, in milliseconds, that the alarm should
+ * be delivered, expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param windowLengthMillis The length of the requested delivery window,
+ * in milliseconds. The alarm will be delivered no later than this many
+ * milliseconds after {@code windowStartMillis}. Note that this parameter
+ * is a <i>duration,</i> not the timestamp of the end of the window.
+ * @param tag Optional. A string describing the alarm, used for logging and battery-use
+ * attribution.
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * called when the alarm time is reached. A given OnAlarmListener instance can
+ * only be the target of a single pending alarm, just as a given PendingIntent
+ * can only be used with one alarm at a time.
+ * @param executor {@link Executor} on which to execute the listener's onAlarm()
+ * callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SCHEDULE_PRIORITIZED_ALARM)
+ public void setPrioritized(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @Nullable String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, FLAG_PRIORITIZE, null, listener,
+ tag, executor, null, null);
+ }
+
+ /**
+ * Schedule an alarm to be delivered precisely at the stated time.
+ *
+ * <p>
+ * This method is like {@link #set(int, long, PendingIntent)}, but does not permit
+ * the OS to adjust the delivery time. The alarm will be delivered as nearly as
+ * possible to the requested trigger time.
+ *
+ * <p>
+ * <b>Note:</b> only alarms for which there is a strong demand for exact-time
+ * delivery (such as an alarm clock ringing at the requested time) should be
+ * scheduled as exact. Applications are strongly discouraged from using exact
+ * alarms unnecessarily as they reduce the OS's ability to minimize battery use.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Starting with {@link Build.VERSION_CODES#S}, apps targeting SDK level 31 or higher
+ * need to request the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
+ * API, unless the app is exempt from battery restrictions.
+ * The user and the system can revoke this permission via the special app access screen in
+ * Settings.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Exact alarms should only be used for user-facing features.
+ * For more details, see <a
+ * href="{@docRoot}about/versions/12/behavior-changes-12#exact-alarm-permission">
+ * Exact alarm permission</a>.
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see #set
+ * @see #setRepeating
+ * @see #setWindow
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
+ */
+ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
+ public void setExact(@AlarmType int type, long triggerAtMillis,
+ @NonNull PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null,
+ null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setExact(int, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ * <p>
+ * This API should only be used to set alarms that are relevant in the context of the app's
+ * current lifecycle, as the {@link OnAlarmListener} instance supplied is only valid as long as
+ * the process is alive, and the system can clean up the app process as soon as it is out of
+ * lifecycle. To schedule alarms that fire reliably even after the current lifecycle completes,
+ * and wakes up the app if required, use any of the other scheduling APIs that accept a
+ * {@link PendingIntent} instance.
+ *
+ * <p>
+ * On previous android versions {@link Build.VERSION_CODES#S} and
+ * {@link Build.VERSION_CODES#TIRAMISU}, apps targeting SDK level 31 or higher needed to hold
+ * the {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use
+ * this API, unless the app was exempt from battery restrictions.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Starting with android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system will
+ * explicitly drop any alarms set via this API when the calling app goes out of lifecycle.
+ *
+ */
+ public void setExact(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+ targetHandler, null, null);
+ }
+
+ /**
+ * Schedule an idle-until alarm, which will keep the alarm manager idle until
+ * the given time.
+ * @hide
+ */
+ public void setIdleUntil(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null,
+ listener, tag, targetHandler, null, null);
+ }
+
+ /**
+ * Schedule an alarm that represents an alarm clock, which will be used to notify the user
+ * when it goes off. The expectation is that when this alarm triggers, the application will
+ * further wake up the device to tell the user about the alarm -- turning on the screen,
+ * playing a sound, vibrating, etc. As such, the system will typically also use the
+ * information supplied here to tell the user about this upcoming alarm if appropriate.
+ *
+ * <p>Due to the nature of this kind of alarm, similar to {@link #setExactAndAllowWhileIdle},
+ * these alarms will be allowed to trigger even if the system is in a low-power idle
+ * (a.k.a. doze) mode. The system may also do some prep-work when it sees that such an
+ * alarm coming up, to reduce the amount of background work that could happen if this
+ * causes the device to fully wake up -- this is to avoid situations such as a large number
+ * of devices having an alarm set at the same time in the morning, all waking up at that
+ * time and suddenly swamping the network with pending background work. As such, these
+ * types of alarms can be extremely expensive on battery use and should only be used for
+ * their intended purpose.</p>
+ *
+ * <p>
+ * This method is like {@link #setExact(int, long, PendingIntent)}, but implies
+ * {@link #RTC_WAKEUP}.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Starting with {@link Build.VERSION_CODES#S}, apps targeting SDK level 31 or higher
+ * need to request the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
+ * API.
+ * The user and the system can revoke this permission via the special app access screen in
+ * Settings.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Exact alarms should only be used for user-facing features.
+ * For more details, see <a
+ * href="{@docRoot}about/versions/12/behavior-changes-12#exact-alarm-permission">
+ * Exact alarm permission</a>.
+ *
+ * <p>Alarms scheduled via this API
+ * will be allowed to start a foreground service even if the app is in the background.
+ *
+ * @param info
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see #set
+ * @see #setRepeating
+ * @see #setWindow
+ * @see #setExact
+ * @see #cancel
+ * @see #getNextAlarmClock()
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
+ */
+ @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
+ public void setAlarmClock(@NonNull AlarmClockInfo info, @NonNull PendingIntent operation) {
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
+ null, null, (Handler) null, null, info);
+ }
+
+ /** @hide */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, @NonNull PendingIntent operation,
+ @Nullable WorkSource workSource) {
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
+ (Handler) null, workSource, null);
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+ * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, @Nullable String tag, @NonNull OnAlarmListener listener,
+ @Nullable Handler targetHandler, @Nullable WorkSource workSource) {
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag,
+ targetHandler, workSource, null);
+ }
+
+ /**
+ * This is only used to make an identifying tag for the deprecated
+ * {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API which doesn't
+ * accept a tag. For all other APIs, the tag provided by the app is used, even if it is
+ * {@code null}.
+ */
+ private static String makeTag(long triggerMillis, WorkSource ws) {
+ final StringBuilder tagBuilder = new StringBuilder(GENERATED_TAG_PREFIX);
+
+ tagBuilder.append(":");
+ final int attributionUid =
+ (ws == null || ws.isEmpty()) ? Process.myUid() : ws.getAttributionUid();
+ tagBuilder.append(UserHandle.formatUid(attributionUid));
+ tagBuilder.append(":");
+ tagBuilder.append(triggerMillis);
+ return tagBuilder.toString();
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+ * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * <p>The behavior of this API when {@code windowMillis < 0} is undefined.
+ *
+ * @deprecated Better alternative APIs exist for setting an alarm with this method:
+ * <ul>
+ * <li>For alarms with {@code windowMillis > 0}, use
+ * {@link #setWindow(int, long, long, String, Executor, WorkSource, OnAlarmListener)}</li>
+ * <li>For alarms with {@code windowMillis = 0}, use
+ * {@link #setExact(int, long, String, Executor, WorkSource, OnAlarmListener)}</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler,
+ @Nullable WorkSource workSource) {
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener,
+ makeTag(triggerAtMillis, workSource), targetHandler, workSource, null);
+ }
+
+ /**
+ * Exact version of {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)}.
+ * This equivalent to calling the aforementioned API with {@code windowMillis} and
+ * {@code intervalMillis} set to 0.
+ * One subtle difference is that this API requires {@code workSource} to be non-null. If you
+ * don't want to attribute this alarm to another app for battery consumption, you should use
+ * {@link #setExact(int, long, String, OnAlarmListener, Handler)} instead.
+ *
+ * <p>
+ * Note that on previous Android versions {@link Build.VERSION_CODES#S} and
+ * {@link Build.VERSION_CODES#TIRAMISU}, using this API required you to hold
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM}, unless you are on the system's power
+ * allowlist. This can be set, for example, by marking the app as {@code <allow-in-power-save>}
+ * within the system config.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Starting with android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system will
+ * explicitly drop any alarms set via this API when the calling app goes out of lifecycle.
+ *
+ * @param type type of alarm
+ * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered,
+ * expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be called when
+ * the alarm time is reached.
+ * @param executor The {@link Executor} on which to execute the listener's onAlarm()
+ * callback.
+ * @param tag Optional. A string tag used to identify this alarm in logs and
+ * battery-attribution.
+ * @param workSource A {@link WorkSource} object to attribute this alarm to the app that
+ * requested this work.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
+ public void setExact(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull Executor executor, @NonNull WorkSource workSource,
+ @NonNull OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(workSource);
+ Objects.requireNonNull(listener);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag, executor,
+ workSource, null);
+ }
+
+
+ private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
+ String listenerTag, Handler targetHandler, WorkSource workSource,
+ AlarmClockInfo alarmClock) {
+ final Handler handlerToUse = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation, listener,
+ listenerTag, new HandlerExecutor(handlerToUse), workSource, alarmClock);
+ }
+
+ private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
+ long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
+ String listenerTag, Executor targetExecutor, WorkSource workSource,
+ AlarmClockInfo alarmClock) {
+ if (triggerAtMillis < 0) {
+ /* NOTYET
+ if (mAlwaysExact) {
+ // Fatal error for KLP+ apps to use negative trigger times
+ throw new IllegalArgumentException("Invalid alarm trigger time "
+ + triggerAtMillis);
+ }
+ */
+ triggerAtMillis = 0;
+ }
+
+ ListenerWrapper recipientWrapper = null;
+ if (listener != null) {
+ synchronized (AlarmManager.class) {
+ if (sWrappers == null) {
+ sWrappers = new WeakHashMap<>();
+ }
+
+ final WeakReference<ListenerWrapper> weakRef = sWrappers.get(listener);
+ if (weakRef != null) {
+ recipientWrapper = weakRef.get();
+ }
+ // no existing wrapper => build a new one
+ if (recipientWrapper == null) {
+ recipientWrapper = new ListenerWrapper(listener);
+ sWrappers.put(listener, new WeakReference<>(recipientWrapper));
+ }
+ }
+ recipientWrapper.setExecutor(targetExecutor);
+ }
+
+ try {
+ mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
+ operation, recipientWrapper, listenerTag, workSource, alarmClock);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Available inexact recurrence interval recognized by
+ * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+ * when running on Android prior to API 19.
+ */
+ public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000;
+
+ /**
+ * Available inexact recurrence interval recognized by
+ * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+ * when running on Android prior to API 19.
+ */
+ public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES;
+
+ /**
+ * Available inexact recurrence interval recognized by
+ * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+ * when running on Android prior to API 19.
+ */
+ public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR;
+
+ /**
+ * Available inexact recurrence interval recognized by
+ * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+ * when running on Android prior to API 19.
+ */
+ public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR;
+
+ /**
+ * Available inexact recurrence interval recognized by
+ * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+ * when running on Android prior to API 19.
+ */
+ public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY;
+
+ /**
+ * Schedule a repeating alarm that has inexact trigger time requirements;
+ * for example, an alarm that repeats every hour, but not necessarily at
+ * the top of every hour. These alarms are more power-efficient than
+ * the strict recurrences traditionally supplied by {@link #setRepeating}, since the
+ * system can adjust alarms' delivery times to cause them to fire simultaneously,
+ * avoiding waking the device from sleep more than necessary.
+ *
+ * <p>Your alarm's first trigger will not be before the requested time,
+ * but it might not occur for almost a full interval after that time. In
+ * addition, while the overall period of the repeating alarm will be as
+ * requested, the time between any two successive firings of the alarm
+ * may vary. If your application demands very low jitter, use
+ * one-shot alarms with an appropriate window instead; see {@link
+ * #setWindow(int, long, long, PendingIntent)} and
+ * {@link #setExact(int, long, PendingIntent)}.
+ *
+ * <p class="note">
+ * As of API 19, all repeating alarms are inexact. Because this method has
+ * been available since API 3, your application can safely call it and be
+ * assured that it will get similar behavior on both current and older versions
+ * of Android.
+ * <p>Apps targeting {@link Build.VERSION_CODES#S} will need to set the flag
+ * {@link PendingIntent#FLAG_MUTABLE} on the {@link PendingIntent} being used to set this alarm,
+ * if they want the alarm count to be supplied with the key {@link Intent#EXTRA_ALARM_COUNT}.
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should first
+ * go off, using the appropriate clock (depending on the alarm type). This
+ * is inexact: the alarm will not fire before this time, but there may be a
+ * delay of almost an entire alarm interval before the first invocation of
+ * the alarm.
+ * @param intervalMillis interval in milliseconds between subsequent repeats
+ * of the alarm. Prior to API 19, if this is one of INTERVAL_FIFTEEN_MINUTES,
+ * INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY
+ * then the alarm will be phase-aligned with other alarms to reduce the
+ * number of wakeups. Otherwise, the alarm will be set as though the
+ * application had called {@link #setRepeating}. As of API 19, all repeating
+ * alarms will be inexact and subject to batching with other alarms regardless
+ * of their stated repeat interval.
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see android.os.Handler
+ * @see #set
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ * @see #INTERVAL_FIFTEEN_MINUTES
+ * @see #INTERVAL_HALF_HOUR
+ * @see #INTERVAL_HOUR
+ * @see #INTERVAL_HALF_DAY
+ * @see #INTERVAL_DAY
+ * @see Intent#EXTRA_ALARM_COUNT
+ */
+ public void setInexactRepeating(@AlarmType int type, long triggerAtMillis,
+ long intervalMillis, @NonNull PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
+ null, (Handler) null, null, null);
+ }
+
+ /**
+ * Like {@link #set(int, long, PendingIntent)}, but this alarm will be allowed to execute
+ * even when the system is in low-power idle (a.k.a. doze) modes. This type of alarm must
+ * <b>only</b> be used for situations where it is actually required that the alarm go off while
+ * in idle -- a reasonable example would be for a calendar notification that should make a
+ * sound so the user is aware of it. When the alarm is dispatched, the app will also be
+ * added to the system's temporary power exemption list for approximately 10 seconds to allow
+ * that application to acquire further wake locks in which to complete its work.</p>
+ *
+ * <p>These alarms can significantly impact the power use
+ * of the device when idle (and thus cause significant battery blame to the app scheduling
+ * them), so they should be used with care. To reduce abuse, there are restrictions on how
+ * frequently these alarms will go off for a particular application.
+ * Under normal system operation, it will not dispatch these
+ * alarms more than about every minute (at which point every such pending alarm is
+ * dispatched); when in low-power idle modes this duration may be significantly longer,
+ * such as 15 minutes.</p>
+ *
+ * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen
+ * out of order with any other alarms, even those from the same app. This will clearly happen
+ * when the device is idle (since this alarm can go off while idle, when any other alarms
+ * from the app will be held until later), but may also happen even when not idle.</p>
+ *
+ * <p>Regardless of the app's target SDK version, this call always allows batching of the
+ * alarm.</p>
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see #set(int, long, PendingIntent)
+ * @see #setExactAndAllowWhileIdle
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ */
+ public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+ @NonNull PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
+ operation, null, null, (Handler) null, null, null);
+ }
+
+ /**
+ * Like {@link #setExact(int, long, PendingIntent)}, but this alarm will be allowed to execute
+ * even when the system is in low-power idle modes. If you don't need exact scheduling of
+ * the alarm but still need to execute while idle, consider using
+ * {@link #setAndAllowWhileIdle}. This type of alarm must <b>only</b>
+ * be used for situations where it is actually required that the alarm go off while in
+ * idle -- a reasonable example would be for a calendar notification that should make a
+ * sound so the user is aware of it. When the alarm is dispatched, the app will also be
+ * added to the system's temporary power exemption list for approximately 10 seconds to allow
+ * that application to acquire further wake locks in which to complete its work.</p>
+ *
+ * <p>These alarms can significantly impact the power use
+ * of the device when idle (and thus cause significant battery blame to the app scheduling
+ * them), so they should be used with care. To reduce abuse, there are restrictions on how
+ * frequently these alarms will go off for a particular application.
+ * Under normal system operation, it will not dispatch these
+ * alarms more than about every minute (at which point every such pending alarm is
+ * dispatched); when in low-power idle modes this duration may be significantly longer,
+ * such as 15 minutes.</p>
+ *
+ * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen
+ * out of order with any other alarms, even those from the same app. This will clearly happen
+ * when the device is idle (since this alarm can go off while idle, when any other alarms
+ * from the app will be held until later), but may also happen even when not idle.
+ * Note that the OS will allow itself more flexibility for scheduling these alarms than
+ * regular exact alarms, since the application has opted into this behavior. When the
+ * device is idle it may take even more liberties with scheduling in order to optimize
+ * for battery life.</p>
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Starting with {@link Build.VERSION_CODES#S}, apps targeting SDK level 31 or higher
+ * need to request the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
+ * API, unless the app is exempt from battery restrictions.
+ * The user and the system can revoke this permission via the special app access screen in
+ * Settings.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Exact alarms should only be used for user-facing features.
+ * For more details, see <a
+ * href="{@docRoot}about/versions/12/behavior-changes-12#exact-alarm-permission">
+ * Exact alarm permission</a>.
+ *
+ * <p>Alarms scheduled via this API
+ * will be allowed to start a foreground service even if the app is in the background.
+ *
+ * @param type type of alarm.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see #set
+ * @see #setRepeating
+ * @see #setWindow
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
+ */
+ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
+ public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+ @NonNull PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
+ null, null, (Handler) null, null, null);
+ }
+
+ /**
+ * Like {@link #setExact(int, long, String, Executor, WorkSource, OnAlarmListener)}, but this
+ * alarm will be allowed to execute even when the system is in low-power idle modes.
+ *
+ * <p> See {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} for more details.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Starting with android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system will
+ * explicitly drop any alarms set via this API when the calling app goes out of lifecycle.
+ *
+ * @param type type of alarm
+ * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered,
+ * expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be called when
+ * the alarm time is reached.
+ * @param executor The {@link Executor} on which to execute the listener's onAlarm()
+ * callback.
+ * @param tag Optional. A string tag used to identify this alarm in logs and
+ * battery-attribution.
+ * @param workSource A {@link WorkSource} object to attribute this alarm to the app that
+ * requested this work.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
+ public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+ @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource,
+ @NonNull OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, null, listener, tag,
+ executor, workSource, null);
+ }
+
+ /**
+ * Remove any alarms with a matching {@link Intent}.
+ * Any alarm, of any type, whose Intent matches this one (as defined by
+ * {@link Intent#filterEquals}), will be canceled.
+ *
+ * @param operation IntentSender which matches a previously added
+ * IntentSender. This parameter must not be {@code null}.
+ *
+ * @see #set
+ */
+ public void cancel(@NonNull PendingIntent operation) {
+ if (operation == null) {
+ final String msg = "cancel() called with a null PendingIntent";
+ if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
+ throw new NullPointerException(msg);
+ } else {
+ Log.e(TAG, msg);
+ return;
+ }
+ }
+
+ try {
+ mService.remove(operation, null);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
+ *
+ * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
+ */
+ public void cancel(@NonNull OnAlarmListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("cancel() called with a null OnAlarmListener");
+ }
+
+ ListenerWrapper wrapper = null;
+ synchronized (AlarmManager.class) {
+ if (sWrappers != null) {
+ final WeakReference<ListenerWrapper> weakRef = sWrappers.get(listener);
+ if (weakRef != null) {
+ wrapper = weakRef.get();
+ }
+ }
+ }
+
+ if (wrapper == null) {
+ Log.w(TAG, "Unrecognized alarm listener " + listener);
+ return;
+ }
+
+ wrapper.cancel();
+ }
+
+ /**
+ * Remove all alarms previously set by the caller, if any.
+ */
+ public void cancelAll() {
+ try {
+ mService.removeAll(mContext.getOpPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the system wall clock time.
+ * Requires the permission android.permission.SET_TIME.
+ *
+ * @param millis time in milliseconds since the Epoch
+ */
+ @RequiresPermission(android.Manifest.permission.SET_TIME)
+ public void setTime(long millis) {
+ try {
+ mService.setTime(millis);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the system's persistent default time zone. This is the time zone for all apps, even
+ * after a reboot. Use {@link java.util.TimeZone#setDefault} if you just want to change the
+ * time zone within your app, and even then prefer to pass an explicit
+ * {@link java.util.TimeZone} to APIs that require it rather than changing the time zone for
+ * all threads.
+ *
+ * <p> On android M and above, it is an error to pass in a non-Olson timezone to this
+ * function. Note that this is a bad idea on all Android releases because POSIX and
+ * the {@code TimeZone} class have opposite interpretations of {@code '+'} and {@code '-'}
+ * in the same non-Olson ID.
+ *
+ * @param timeZone one of the Olson ids from the list returned by
+ * {@link java.util.TimeZone#getAvailableIDs}
+ */
+ @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE)
+ public void setTimeZone(String timeZone) {
+ if (TextUtils.isEmpty(timeZone)) {
+ return;
+ }
+
+ // Reject this timezone if it isn't an Olson zone we recognize.
+ if (mTargetSdkVersion >= Build.VERSION_CODES.M) {
+ boolean hasTimeZone = ZoneInfoDb.getInstance().hasTimeZone(timeZone);
+ if (!hasTimeZone) {
+ throw new IllegalArgumentException("Timezone: " + timeZone + " is not an Olson ID");
+ }
+ }
+
+ try {
+ mService.setTimeZone(timeZone);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public long getNextWakeFromIdleTime() {
+ try {
+ return mService.getNextWakeFromIdleTime();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called to check if the caller can schedule exact alarms.
+ * Your app schedules exact alarms when it calls any of the {@code setExact...} or
+ * {@link #setAlarmClock(AlarmClockInfo, PendingIntent) setAlarmClock} API methods.
+ * <p>
+ * Apps targeting {@link Build.VERSION_CODES#S} or higher can schedule exact alarms only if they
+ * have the {@link Manifest.permission#SCHEDULE_EXACT_ALARM} permission or they are on the
+ * device's power-save exemption list.
+ * These apps can also
+ * start {@link android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM} to
+ * request this permission from the user.
+ * <p>
+ * Apps targeting lower sdk versions, can always schedule exact alarms.
+ *
+ * @return {@code true} if the caller can schedule exact alarms, {@code false} otherwise.
+ * @see android.provider.Settings#ACTION_REQUEST_SCHEDULE_EXACT_ALARM
+ * @see #setExact(int, long, PendingIntent)
+ * @see #setExactAndAllowWhileIdle(int, long, PendingIntent)
+ * @see #setAlarmClock(AlarmClockInfo, PendingIntent)
+ * @see android.os.PowerManager#isIgnoringBatteryOptimizations(String)
+ */
+ public boolean canScheduleExactAlarms() {
+ try {
+ return mService.canScheduleExactAlarms(mContext.getOpPackageName());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called to check if the given package in the given user has the permission
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM}.
+ *
+ * <p><em>Note: This is only for use by system components.</em>
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean hasScheduleExactAlarm(@NonNull String packageName, int userId) {
+ try {
+ return mService.hasScheduleExactAlarm(packageName, userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets information about the next alarm clock currently scheduled.
+ *
+ * The alarm clocks considered are those scheduled by any application
+ * using the {@link #setAlarmClock} method.
+ *
+ * @return An {@link AlarmClockInfo} object describing the next upcoming alarm
+ * clock event that will occur. If there are no alarm clock events currently
+ * scheduled, this method will return {@code null}.
+ *
+ * @see #setAlarmClock
+ * @see AlarmClockInfo
+ * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
+ */
+ public AlarmClockInfo getNextAlarmClock() {
+ return getNextAlarmClock(mContext.getUserId());
+ }
+
+ /**
+ * Gets information about the next alarm clock currently scheduled.
+ *
+ * The alarm clocks considered are those scheduled by any application
+ * using the {@link #setAlarmClock} method within the given user.
+ *
+ * @return An {@link AlarmClockInfo} object describing the next upcoming alarm
+ * clock event that will occur within the given user. If there are no alarm clock
+ * events currently scheduled in that user, this method will return {@code null}.
+ *
+ * @see #setAlarmClock
+ * @see AlarmClockInfo
+ * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
+ *
+ * @hide
+ */
+ public AlarmClockInfo getNextAlarmClock(int userId) {
+ try {
+ return mService.getNextAlarmClock(userId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * An immutable description of a scheduled "alarm clock" event.
+ *
+ * @see AlarmManager#setAlarmClock
+ * @see AlarmManager#getNextAlarmClock
+ */
+ public static final class AlarmClockInfo implements Parcelable {
+
+ private final long mTriggerTime;
+ private final PendingIntent mShowIntent;
+
+ /**
+ * Creates a new alarm clock description.
+ *
+ * @param triggerTime time at which the underlying alarm is triggered in wall time
+ * milliseconds since the epoch
+ * @param showIntent an intent that can be used to show or edit details of
+ * the alarm clock.
+ */
+ public AlarmClockInfo(long triggerTime, PendingIntent showIntent) {
+ mTriggerTime = triggerTime;
+ mShowIntent = showIntent;
+ }
+
+ /**
+ * Use the {@link #CREATOR}
+ * @hide
+ */
+ @SuppressWarnings("UnsafeParcelApi")
+ AlarmClockInfo(Parcel in) {
+ mTriggerTime = in.readLong();
+ mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+ }
+
+ /**
+ * Returns the time at which the alarm is going to trigger.
+ *
+ * This value is UTC wall clock time in milliseconds, as returned by
+ * {@link System#currentTimeMillis()} for example.
+ */
+ public long getTriggerTime() {
+ return mTriggerTime;
+ }
+
+ /**
+ * Returns an intent that can be used to show or edit details of the alarm clock in
+ * the application that scheduled it.
+ *
+ * <p class="note">Beware that any application can retrieve and send this intent,
+ * potentially with additional fields filled in. See
+ * {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
+ * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
+ * for details.
+ */
+ public PendingIntent getShowIntent() {
+ return mShowIntent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mTriggerTime);
+ dest.writeParcelable(mShowIntent, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<AlarmClockInfo> CREATOR = new Creator<AlarmClockInfo>() {
+ @Override
+ public AlarmClockInfo createFromParcel(Parcel in) {
+ return new AlarmClockInfo(in);
+ }
+
+ @Override
+ public AlarmClockInfo[] newArray(int size) {
+ return new AlarmClockInfo[size];
+ }
+ };
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+ if (mShowIntent != null) {
+ mShowIntent.dumpDebug(proto, AlarmClockInfoProto.SHOW_INTENT);
+ }
+ proto.end(token);
+ }
+ }
+}
diff --git a/android-34/android/app/AlertDialog.java b/android-34/android/app/AlertDialog.java
new file mode 100644
index 0000000..702b960
--- /dev/null
+++ b/android-34/android/app/AlertDialog.java
@@ -0,0 +1,1136 @@
+/*
+ * 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.app;
+
+import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.ResourceId;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Message;
+import android.text.Layout;
+import android.text.method.MovementMethod;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertController;
+
+/**
+ * A subclass of Dialog that can display one, two or three buttons. If you only want to
+ * display a String in this dialog box, use the setMessage() method. If you
+ * want to display a more complex view, look up the FrameLayout called "custom"
+ * and add your view to it:
+ *
+ * <pre>
+ * FrameLayout fl = findViewById(android.R.id.custom);
+ * fl.addView(myView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ * </pre>
+ *
+ * <p>The AlertDialog class takes care of automatically setting
+ * {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
+ * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether
+ * any views in the dialog return true from {@link View#onCheckIsTextEditor()
+ * View.onCheckIsTextEditor()}. Generally you want this set for a Dialog
+ * without text editors, so that it will be placed on top of the current
+ * input method UI. You can modify this behavior by forcing the flag to your
+ * desired mode after calling {@link #onCreate}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating dialogs, read the
+ * <a href="{@docRoot}guide/topics/ui/dialogs.html">Dialogs</a> developer guide.</p>
+ * </div>
+ */
+public class AlertDialog extends Dialog implements DialogInterface {
+ @UnsupportedAppUsage
+ private AlertController mAlert;
+
+ /**
+ * Special theme constant for {@link #AlertDialog(Context, int)}: use
+ * the traditional (pre-Holo) alert dialog theme.
+ *
+ * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
+ */
+ @Deprecated
+ public static final int THEME_TRADITIONAL = 1;
+
+ /**
+ * Special theme constant for {@link #AlertDialog(Context, int)}: use
+ * the holographic alert theme with a dark background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
+ */
+ @Deprecated
+ public static final int THEME_HOLO_DARK = 2;
+
+ /**
+ * Special theme constant for {@link #AlertDialog(Context, int)}: use
+ * the holographic alert theme with a light background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_Material_Light_Dialog_Alert}.
+ */
+ @Deprecated
+ public static final int THEME_HOLO_LIGHT = 3;
+
+ /**
+ * Special theme constant for {@link #AlertDialog(Context, int)}: use
+ * the device's default alert theme with a dark background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_DeviceDefault_Dialog_Alert}.
+ */
+ @Deprecated
+ public static final int THEME_DEVICE_DEFAULT_DARK = 4;
+
+ /**
+ * Special theme constant for {@link #AlertDialog(Context, int)}: use
+ * the device's default alert theme with a light background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_DeviceDefault_Light_Dialog_Alert}.
+ */
+ @Deprecated
+ public static final int THEME_DEVICE_DEFAULT_LIGHT = 5;
+
+ /**
+ * No layout hint.
+ * @hide
+ */
+ public static final int LAYOUT_HINT_NONE = 0;
+
+ /**
+ * Hint layout to the side.
+ * @hide
+ */
+ public static final int LAYOUT_HINT_SIDE = 1;
+
+ /**
+ * Creates an alert dialog that uses the default alert dialog theme.
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
+ * @see android.R.styleable#Theme_alertDialogTheme
+ */
+ protected AlertDialog(Context context) {
+ this(context, 0);
+ }
+
+ /**
+ * Creates an alert dialog that uses the default alert dialog theme and a
+ * custom cancel listener.
+ * <p>
+ * This is functionally identical to:
+ * <pre>
+ * AlertDialog dialog = new AlertDialog(context);
+ * alertDialog.setCancelable(cancelable);
+ * alertDialog.setOnCancelListener(cancelListener);
+ * </pre>
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
+ * @see android.R.styleable#Theme_alertDialogTheme
+ */
+ protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
+ this(context, 0);
+
+ setCancelable(cancelable);
+ setOnCancelListener(cancelListener);
+ }
+
+ /**
+ * Creates an alert dialog that uses an explicit theme resource.
+ * <p>
+ * The specified theme resource ({@code themeResId}) is applied on top of
+ * the parent {@code context}'s theme. It may be specified as a style
+ * resource containing a fully-populated theme, such as
+ * {@link android.R.style#Theme_Material_Dialog}, to replace all attributes
+ * in the parent {@code context}'s theme including primary and accent
+ * colors.
+ * <p>
+ * To preserve attributes such as primary and accent colors, the
+ * {@code themeResId} may instead be specified as an overlay theme such as
+ * {@link android.R.style#ThemeOverlay_Material_Dialog}. This will override
+ * only the window attributes necessary to style the alert window as a
+ * dialog.
+ * <p>
+ * Alternatively, the {@code themeResId} may be specified as {@code 0} to
+ * use the parent {@code context}'s resolved value for
+ * {@link android.R.attr#alertDialogTheme}.
+ *
+ * @param context the parent context
+ * @param themeResId the resource ID of the theme against which to inflate
+ * this dialog, or {@code 0} to use the parent
+ * {@code context}'s default alert dialog theme
+ * @see android.R.styleable#Theme_alertDialogTheme
+ */
+ protected AlertDialog(Context context, @StyleRes int themeResId) {
+ this(context, themeResId, true);
+ }
+
+ AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
+ super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
+ createContextThemeWrapper);
+
+ mWindow.alwaysReadCloseOnTouchAttr();
+ mAlert = AlertController.create(getContext(), this, getWindow());
+ }
+
+ static @StyleRes int resolveDialogTheme(Context context, @StyleRes int themeResId) {
+ if (themeResId == THEME_TRADITIONAL) {
+ return R.style.Theme_Dialog_Alert;
+ } else if (themeResId == THEME_HOLO_DARK) {
+ return R.style.Theme_Holo_Dialog_Alert;
+ } else if (themeResId == THEME_HOLO_LIGHT) {
+ return R.style.Theme_Holo_Light_Dialog_Alert;
+ } else if (themeResId == THEME_DEVICE_DEFAULT_DARK) {
+ return R.style.Theme_DeviceDefault_Dialog_Alert;
+ } else if (themeResId == THEME_DEVICE_DEFAULT_LIGHT) {
+ return R.style.Theme_DeviceDefault_Light_Dialog_Alert;
+ } else if (ResourceId.isValid(themeResId)) {
+ // start of real resource IDs.
+ return themeResId;
+ } else {
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
+ return outValue.resourceId;
+ }
+ }
+
+ /**
+ * Gets one of the buttons used in the dialog. Returns null if the specified
+ * button does not exist or the dialog has not yet been fully created (for
+ * example, via {@link #show()} or {@link #create()}).
+ *
+ * @param whichButton The identifier of the button that should be returned.
+ * For example, this can be
+ * {@link DialogInterface#BUTTON_POSITIVE}.
+ * @return The button from the dialog, or null if a button does not exist.
+ */
+ public Button getButton(int whichButton) {
+ return mAlert.getButton(whichButton);
+ }
+
+ /**
+ * Gets the list view used in the dialog.
+ *
+ * @return The {@link ListView} from the dialog.
+ */
+ public ListView getListView() {
+ return mAlert.getListView();
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ super.setTitle(title);
+ mAlert.setTitle(title);
+ }
+
+ /**
+ * @see Builder#setCustomTitle(View)
+ */
+ public void setCustomTitle(View customTitleView) {
+ mAlert.setCustomTitle(customTitleView);
+ }
+
+ public void setMessage(CharSequence message) {
+ mAlert.setMessage(message);
+ }
+
+ /** @hide */
+ public void setMessageMovementMethod(MovementMethod movementMethod) {
+ mAlert.setMessageMovementMethod(movementMethod);
+ }
+
+ /** @hide */
+ public void setMessageHyphenationFrequency(
+ @Layout.HyphenationFrequency int hyphenationFrequency) {
+ mAlert.setMessageHyphenationFrequency(hyphenationFrequency);
+ }
+
+ /**
+ * Set the view to display in that dialog.
+ */
+ public void setView(View view) {
+ mAlert.setView(view);
+ }
+
+ /**
+ * Set the view to display in that dialog, specifying the spacing to appear around that
+ * view.
+ *
+ * @param view The view to show in the content area of the dialog
+ * @param viewSpacingLeft Extra space to appear to the left of {@code view}
+ * @param viewSpacingTop Extra space to appear above {@code view}
+ * @param viewSpacingRight Extra space to appear to the right of {@code view}
+ * @param viewSpacingBottom Extra space to appear below {@code view}
+ */
+ public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
+ int viewSpacingBottom) {
+ mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
+ }
+
+ /**
+ * Internal api to allow hinting for the best button panel layout.
+ * @hide
+ */
+ void setButtonPanelLayoutHint(int layoutHint) {
+ mAlert.setButtonPanelLayoutHint(layoutHint);
+ }
+
+ /**
+ * Set a message to be sent when a button is pressed.
+ *
+ * @param whichButton Which button to set the message for, can be one of
+ * {@link DialogInterface#BUTTON_POSITIVE},
+ * {@link DialogInterface#BUTTON_NEGATIVE}, or
+ * {@link DialogInterface#BUTTON_NEUTRAL}
+ * @param text The text to display in positive button.
+ * @param msg The {@link Message} to be sent when clicked.
+ */
+ public void setButton(int whichButton, CharSequence text, Message msg) {
+ mAlert.setButton(whichButton, text, null, msg);
+ }
+
+ /**
+ * Set a listener to be invoked when the specified button of the dialog is pressed.
+ *
+ * @param whichButton Which button to set the listener on, can be one of
+ * {@link DialogInterface#BUTTON_POSITIVE},
+ * {@link DialogInterface#BUTTON_NEGATIVE}, or
+ * {@link DialogInterface#BUTTON_NEUTRAL}
+ * @param text The text to display in positive button.
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ */
+ public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
+ mAlert.setButton(whichButton, text, listener, null);
+ }
+
+ /**
+ * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+ * {@link DialogInterface#BUTTON_POSITIVE}.
+ */
+ @Deprecated
+ public void setButton(CharSequence text, Message msg) {
+ setButton(BUTTON_POSITIVE, text, msg);
+ }
+
+ /**
+ * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+ * {@link DialogInterface#BUTTON_NEGATIVE}.
+ */
+ @Deprecated
+ public void setButton2(CharSequence text, Message msg) {
+ setButton(BUTTON_NEGATIVE, text, msg);
+ }
+
+ /**
+ * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+ * {@link DialogInterface#BUTTON_NEUTRAL}.
+ */
+ @Deprecated
+ public void setButton3(CharSequence text, Message msg) {
+ setButton(BUTTON_NEUTRAL, text, msg);
+ }
+
+ /**
+ * Set a listener to be invoked when button 1 of the dialog is pressed.
+ *
+ * @param text The text to display in button 1.
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @deprecated Use
+ * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+ * with {@link DialogInterface#BUTTON_POSITIVE}
+ */
+ @Deprecated
+ public void setButton(CharSequence text, final OnClickListener listener) {
+ setButton(BUTTON_POSITIVE, text, listener);
+ }
+
+ /**
+ * Set a listener to be invoked when button 2 of the dialog is pressed.
+ * @param text The text to display in button 2.
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @deprecated Use
+ * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+ * with {@link DialogInterface#BUTTON_NEGATIVE}
+ */
+ @Deprecated
+ public void setButton2(CharSequence text, final OnClickListener listener) {
+ setButton(BUTTON_NEGATIVE, text, listener);
+ }
+
+ /**
+ * Set a listener to be invoked when button 3 of the dialog is pressed.
+ * @param text The text to display in button 3.
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @deprecated Use
+ * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+ * with {@link DialogInterface#BUTTON_NEUTRAL}
+ */
+ @Deprecated
+ public void setButton3(CharSequence text, final OnClickListener listener) {
+ setButton(BUTTON_NEUTRAL, text, listener);
+ }
+
+ /**
+ * Set resId to 0 if you don't want an icon.
+ * @param resId the resourceId of the drawable to use as the icon or 0
+ * if you don't want an icon.
+ */
+ public void setIcon(@DrawableRes int resId) {
+ mAlert.setIcon(resId);
+ }
+
+ public void setIcon(Drawable icon) {
+ mAlert.setIcon(icon);
+ }
+
+ /**
+ * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon
+ *
+ * @param attrId ID of a theme attribute that points to a drawable resource.
+ */
+ public void setIconAttribute(@AttrRes int attrId) {
+ TypedValue out = new TypedValue();
+ mContext.getTheme().resolveAttribute(attrId, out, true);
+ mAlert.setIcon(out.resourceId);
+ }
+
+ public void setInverseBackgroundForced(boolean forceInverseBackground) {
+ mAlert.setInverseBackgroundForced(forceInverseBackground);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAlert.installContent();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyDown(keyCode, event)) return true;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyUp(keyCode, event)) return true;
+ return super.onKeyUp(keyCode, event);
+ }
+
+ public static class Builder {
+ @UnsupportedAppUsage
+ private final AlertController.AlertParams P;
+
+ /**
+ * Creates a builder for an alert dialog that uses the default alert
+ * dialog theme.
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
+ */
+ public Builder(Context context) {
+ this(context, resolveDialogTheme(context, Resources.ID_NULL));
+ }
+
+ /**
+ * Creates a builder for an alert dialog that uses an explicit theme
+ * resource.
+ * <p>
+ * The specified theme resource ({@code themeResId}) is applied on top
+ * of the parent {@code context}'s theme. It may be specified as a
+ * style resource containing a fully-populated theme, such as
+ * {@link android.R.style#Theme_Material_Dialog}, to replace all
+ * attributes in the parent {@code context}'s theme including primary
+ * and accent colors.
+ * <p>
+ * To preserve attributes such as primary and accent colors, the
+ * {@code themeResId} may instead be specified as an overlay theme such
+ * as {@link android.R.style#ThemeOverlay_Material_Dialog}. This will
+ * override only the window attributes necessary to style the alert
+ * window as a dialog.
+ * <p>
+ * Alternatively, the {@code themeResId} may be specified as {@code 0}
+ * to use the parent {@code context}'s resolved value for
+ * {@link android.R.attr#alertDialogTheme}.
+ *
+ * @param context the parent context
+ * @param themeResId the resource ID of the theme against which to inflate
+ * this dialog, or {@code 0} to use the parent
+ * {@code context}'s default alert dialog theme
+ */
+ public Builder(Context context, int themeResId) {
+ P = new AlertController.AlertParams(new ContextThemeWrapper(
+ context, resolveDialogTheme(context, themeResId)));
+ }
+
+ /**
+ * Returns a {@link Context} with the appropriate theme for dialogs created by this Builder.
+ * Applications should use this Context for obtaining LayoutInflaters for inflating views
+ * that will be used in the resulting dialogs, as it will cause views to be inflated with
+ * the correct theme.
+ *
+ * @return A Context for built Dialogs.
+ */
+ public Context getContext() {
+ return P.mContext;
+ }
+
+ /**
+ * Set the title using the given resource id.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setTitle(@StringRes int titleId) {
+ P.mTitle = P.mContext.getText(titleId);
+ return this;
+ }
+
+ /**
+ * Set the title displayed in the {@link Dialog}.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setTitle(CharSequence title) {
+ P.mTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the title using the custom view {@code customTitleView}.
+ * <p>
+ * The methods {@link #setTitle(int)} and {@link #setIcon(int)} should
+ * be sufficient for most titles, but this is provided if the title
+ * needs more customization. Using this will replace the title and icon
+ * set via the other methods.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the custom view
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
+ *
+ * @param customTitleView the custom view to use as the title
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
+ */
+ public Builder setCustomTitle(View customTitleView) {
+ P.mCustomTitleView = customTitleView;
+ return this;
+ }
+
+ /**
+ * Set the message to display using the given resource id.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setMessage(@StringRes int messageId) {
+ P.mMessage = P.mContext.getText(messageId);
+ return this;
+ }
+
+ /**
+ * Set the message to display.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setMessage(CharSequence message) {
+ P.mMessage = message;
+ return this;
+ }
+
+ /**
+ * Set the resource id of the {@link Drawable} to be used in the title.
+ * <p>
+ * Takes precedence over values set using {@link #setIcon(Drawable)}.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setIcon(@DrawableRes int iconId) {
+ P.mIconId = iconId;
+ return this;
+ }
+
+ /**
+ * Set the {@link Drawable} to be used in the title.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the drawable
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
+ *
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
+ */
+ public Builder setIcon(Drawable icon) {
+ P.mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Set an icon as supplied by a theme attribute. e.g.
+ * {@link android.R.attr#alertDialogIcon}.
+ * <p>
+ * Takes precedence over values set using {@link #setIcon(int)} or
+ * {@link #setIcon(Drawable)}.
+ *
+ * @param attrId ID of a theme attribute that points to a drawable resource.
+ */
+ public Builder setIconAttribute(@AttrRes int attrId) {
+ TypedValue out = new TypedValue();
+ P.mContext.getTheme().resolveAttribute(attrId, out, true);
+ P.mIconId = out.resourceId;
+ return this;
+ }
+
+ /**
+ * Set a listener to be invoked when the positive button of the dialog is pressed.
+ * @param textId The resource id of the text to display in the positive button
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
+ P.mPositiveButtonText = P.mContext.getText(textId);
+ P.mPositiveButtonListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a listener to be invoked when the positive button of the dialog is pressed.
+ * @param text The text to display in the positive button
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
+ P.mPositiveButtonText = text;
+ P.mPositiveButtonListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a listener to be invoked when the negative button of the dialog is pressed.
+ * @param textId The resource id of the text to display in the negative button
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
+ P.mNegativeButtonText = P.mContext.getText(textId);
+ P.mNegativeButtonListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a listener to be invoked when the negative button of the dialog is pressed.
+ * @param text The text to display in the negative button
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
+ P.mNegativeButtonText = text;
+ P.mNegativeButtonListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a listener to be invoked when the neutral button of the dialog is pressed.
+ * @param textId The resource id of the text to display in the neutral button
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) {
+ P.mNeutralButtonText = P.mContext.getText(textId);
+ P.mNeutralButtonListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a listener to be invoked when the neutral button of the dialog is pressed.
+ * @param text The text to display in the neutral button
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
+ P.mNeutralButtonText = text;
+ P.mNeutralButtonListener = listener;
+ return this;
+ }
+
+ /**
+ * Sets whether the dialog is cancelable or not. Default is true.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setCancelable(boolean cancelable) {
+ P.mCancelable = cancelable;
+ return this;
+ }
+
+ /**
+ * Sets the callback that will be called if the dialog is canceled.
+ *
+ * <p>Even in a cancelable dialog, the dialog may be dismissed for reasons other than
+ * being canceled or one of the supplied choices being selected.
+ * If you are interested in listening for all cases where the dialog is dismissed
+ * and not just when it is canceled, see
+ * {@link #setOnDismissListener(android.content.DialogInterface.OnDismissListener) setOnDismissListener}.</p>
+ * @see #setCancelable(boolean)
+ * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setOnCancelListener(OnCancelListener onCancelListener) {
+ P.mOnCancelListener = onCancelListener;
+ return this;
+ }
+
+ /**
+ * Sets the callback that will be called when the dialog is dismissed for any reason.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setOnDismissListener(OnDismissListener onDismissListener) {
+ P.mOnDismissListener = onDismissListener;
+ return this;
+ }
+
+ /**
+ * Sets the callback that will be called if a key is dispatched to the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setOnKeyListener(OnKeyListener onKeyListener) {
+ P.mOnKeyListener = onKeyListener;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of the
+ * selected item via the supplied listener. This should be an array type i.e. R.array.foo
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
+ P.mItems = P.mContext.getResources().getTextArray(itemsId);
+ P.mOnClickListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of the
+ * selected item via the supplied listener.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setItems(CharSequence[] items, final OnClickListener listener) {
+ P.mItems = items;
+ P.mOnClickListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
+ * displayed in the dialog as the content, you will be notified of the
+ * selected item via the supplied listener.
+ *
+ * @param adapter The {@link ListAdapter} to supply the list of items
+ * @param listener The listener that will be called when an item is clicked.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) {
+ P.mAdapter = adapter;
+ P.mOnClickListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a list of items, which are supplied by the given {@link Cursor}, to be
+ * displayed in the dialog as the content, you will be notified of the
+ * selected item via the supplied listener.
+ *
+ * @param cursor The {@link Cursor} to supply the list of items
+ * @param listener The listener that will be called when an item is clicked.
+ * @param labelColumn The column name on the cursor containing the string to display
+ * in the label.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setCursor(final Cursor cursor, final OnClickListener listener,
+ String labelColumn) {
+ P.mCursor = cursor;
+ P.mLabelColumn = labelColumn;
+ P.mOnClickListener = listener;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content,
+ * you will be notified of the selected item via the supplied listener.
+ * This should be an array type, e.g. R.array.foo. The list will have
+ * a check mark displayed to the right of the text for each checked
+ * item. Clicking on an item in the list will not dismiss the dialog.
+ * Clicking on a button will dismiss the dialog.
+ *
+ * @param itemsId the resource id of an array i.e. R.array.foo
+ * @param checkedItems specifies which items are checked. It should be null in which case no
+ * items are checked. If non null it must be exactly the same length as the array of
+ * items.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
+ final OnMultiChoiceClickListener listener) {
+ P.mItems = P.mContext.getResources().getTextArray(itemsId);
+ P.mOnCheckboxClickListener = listener;
+ P.mCheckedItems = checkedItems;
+ P.mIsMultiChoice = true;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content,
+ * you will be notified of the selected item via the supplied listener.
+ * The list will have a check mark displayed to the right of the text
+ * for each checked item. Clicking on an item in the list will not
+ * dismiss the dialog. Clicking on a button will dismiss the dialog.
+ *
+ * @param items the text of the items to be displayed in the list.
+ * @param checkedItems specifies which items are checked. It should be null in which case no
+ * items are checked. If non null it must be exactly the same length as the array of
+ * items.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
+ final OnMultiChoiceClickListener listener) {
+ P.mItems = items;
+ P.mOnCheckboxClickListener = listener;
+ P.mCheckedItems = checkedItems;
+ P.mIsMultiChoice = true;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content,
+ * you will be notified of the selected item via the supplied listener.
+ * The list will have a check mark displayed to the right of the text
+ * for each checked item. Clicking on an item in the list will not
+ * dismiss the dialog. Clicking on a button will dismiss the dialog.
+ *
+ * @param cursor the cursor used to provide the items.
+ * @param isCheckedColumn specifies the column name on the cursor to use to determine
+ * whether a checkbox is checked or not. It must return an integer value where 1
+ * means checked and 0 means unchecked.
+ * @param labelColumn The column name on the cursor containing the string to display in the
+ * label.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn,
+ final OnMultiChoiceClickListener listener) {
+ P.mCursor = cursor;
+ P.mOnCheckboxClickListener = listener;
+ P.mIsCheckedColumn = isCheckedColumn;
+ P.mLabelColumn = labelColumn;
+ P.mIsMultiChoice = true;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. This should be an array type i.e.
+ * R.array.foo The list will have a check mark displayed to the right of the text for the
+ * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
+ * button will dismiss the dialog.
+ *
+ * @param itemsId the resource id of an array i.e. R.array.foo
+ * @param checkedItem specifies which item is checked. If -1 no items are checked.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
+ final OnClickListener listener) {
+ P.mItems = P.mContext.getResources().getTextArray(itemsId);
+ P.mOnClickListener = listener;
+ P.mCheckedItem = checkedItem;
+ P.mIsSingleChoice = true;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. The list will have a check mark displayed to
+ * the right of the text for the checked item. Clicking on an item in the list will not
+ * dismiss the dialog. Clicking on a button will dismiss the dialog.
+ *
+ * @param cursor the cursor to retrieve the items from.
+ * @param checkedItem specifies which item is checked. If -1 no items are checked.
+ * @param labelColumn The column name on the cursor containing the string to display in the
+ * label.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn,
+ final OnClickListener listener) {
+ P.mCursor = cursor;
+ P.mOnClickListener = listener;
+ P.mCheckedItem = checkedItem;
+ P.mLabelColumn = labelColumn;
+ P.mIsSingleChoice = true;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. The list will have a check mark displayed to
+ * the right of the text for the checked item. Clicking on an item in the list will not
+ * dismiss the dialog. Clicking on a button will dismiss the dialog.
+ *
+ * @param items the items to be displayed.
+ * @param checkedItem specifies which item is checked. If -1 no items are checked.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) {
+ P.mItems = items;
+ P.mOnClickListener = listener;
+ P.mCheckedItem = checkedItem;
+ P.mIsSingleChoice = true;
+ return this;
+ }
+
+ /**
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. The list will have a check mark displayed to
+ * the right of the text for the checked item. Clicking on an item in the list will not
+ * dismiss the dialog. Clicking on a button will dismiss the dialog.
+ *
+ * @param adapter The {@link ListAdapter} to supply the list of items
+ * @param checkedItem specifies which item is checked. If -1 no items are checked.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listene