Add SDK 29 sources. Test: N/A Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/accessibilityservice/AccessibilityButtonController.java b/android/accessibilityservice/AccessibilityButtonController.java new file mode 100644 index 0000000..af5af9c --- /dev/null +++ b/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 com.android.internal.util.Preconditions; + +/** + * 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) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(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) { + Preconditions.checkNotNull(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/accessibilityservice/AccessibilityService.java b/android/accessibilityservice/AccessibilityService.java new file mode 100644 index 0000000..cebe6e1 --- /dev/null +++ b/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,1986 @@ +/* + * 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 android.accessibilityservice.GestureDescription.MotionEventGenerator; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.UnsupportedAppUsage; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ParceledListSlice; +import android.graphics.Region; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; +import android.view.KeyEvent; +import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; + +import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * 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> + * <ol> + * Specify that it handles the "android.accessibilityservice.AccessibilityService" + * {@link android.content.Intent}. + * </ol> + * <ol> + * Request the {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to + * ensure that only the system can bind to it. + * </ol> + * </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>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_AUDIBLE}</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 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 {@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 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 + */ + 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; + + private static final String LOG_TAG = "AccessibilityService"; + + /** + * Interface used by IAccessibilityServiceWrapper 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); + boolean onGesture(int gestureId); + boolean onKeyEvent(KeyEvent event); + /** Magnification changed callbacks for different displays */ + void onMagnificationChanged(int displayId, @NonNull Region region, + float scale, float centerX, float centerY); + void onSoftKeyboardShowModeChanged(int showMode); + void onPerformGestureResult(int sequence, boolean completedSuccessfully); + void onFingerprintCapturingGesturesChanged(boolean active); + void onFingerprintGesture(int gesture); + void onAccessibilityButtonClicked(); + void onAccessibilityButtonAvailabilityChanged(boolean available); + } + + /** + * 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; + + 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); + private SoftKeyboardController mSoftKeyboardController; + private AccessibilityButtonController mAccessibilityButtonController; + + private int mGestureStatusCallbackSequence; + + private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos; + + private final Object mLock = new Object(); + + private FingerprintGestureController mFingerprintGestureController; + + /** + * 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(); + } + } + 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(); + } + + /** + * 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 the system when the user performs a specific gesture on the + * touch screen. + * + * <strong>Note:</strong> To receive gestures an accessibility service must + * request that the device is in touch exploration mode by setting the + * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} + * flag. + * + * @param gestureId The unique id of the performed gesture. + * + * @return Whether the gesture was handled. + * + * @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 + */ + protected boolean onGesture(int gestureId) { + 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; + } + + /** + * Gets the windows on the screen. 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().getWindows(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. + * <p> + * The currently active window is defined as the window that most recently fired one + * of the following events: + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}. + * In other words, the last window shown that also has input focus. + * </p> + * <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. + */ + public AccessibilityNodeInfo getRootInActiveWindow() { + return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId); + } + + /** + * 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().getConnection(mConnectionId); + if (connection != null) { + try { + connection.disableSelf(); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + } + + /** + * 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().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().getConnection( + mConnectionId); + if (connection == null) { + return false; + } + List<GestureDescription.GestureStep> steps = + MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 100); + 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.sendGesture(mGestureStatusCallbackSequence, + new ParceledListSlice<>(steps)); + } + } catch (RemoteException re) { + throw new RuntimeException(re); + } + return true; + } + + void onPerformGestureResult(int sequence, final boolean completedSuccessfully) { + if (mGestureStatusCallbackInfos == null) { + return; + } + GestureResultCallbackInfo callbackInfo; + synchronized (mLock) { + callbackInfo = mGestureStatusCallbackInfos.get(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, float scale, + float centerX, float centerY) { + MagnificationController controller; + synchronized (mLock) { + controller = mMagnificationControllers.get(displayId); + } + if (controller != null) { + controller.dispatchMagnificationChanged(region, scale, centerX, centerY); + } + } + + /** + * 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); + } + + /** + * 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().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 float scale, + final float centerX, final float centerY) { + 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(new Runnable() { + @Override + public void run() { + listener.onMagnificationChanged(MagnificationController.this, + region, scale, centerX, centerY); + } + }); + } else { + // We're already on the main thread, just run the listener. + listener.onMagnificationChanged(this, region, scale, centerX, centerY); + } + } + } + + /** + * 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}. + * + * @return the current magnification scale + */ + public float getScale() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().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}. + * + * @return the unscaled screen-relative X coordinate of the center of + * the magnified region + */ + public float getCenterX() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().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}. + * + * @return the unscaled screen-relative Y coordinate of the center of + * the magnified region + */ + public float getCenterY() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().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. + * + * @return the region of the screen currently active for magnification, or an empty region + * if magnification is not active. + */ + @NonNull + public Region getMagnificationRegion() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().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(); + } + + /** + * 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}. + * + * @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().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; + } + + /** + * 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}. + * + * @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 + */ + public boolean setScale(float scale, boolean animate) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.setMagnificationScaleAndCenter(mDisplayId, + scale, Float.NaN, Float.NaN, 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}. + * + * @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 + */ + public boolean setCenter(float centerX, float centerY, boolean animate) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.setMagnificationScaleAndCenter(mDisplayId, + Float.NaN, centerX, centerY, 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. + * + * @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 + */ + void onMagnificationChanged(@NonNull MagnificationController controller, + @NonNull Region region, float scale, float centerX, float centerY); + } + } + + /** + * 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; + } + } + + 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; + + 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().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().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().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); + } + } + + /** + * 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() { + synchronized (mLock) { + if (mAccessibilityButtonController == null) { + mAccessibilityButtonController = new AccessibilityButtonController( + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId)); + } + return mAccessibilityButtonController; + } + } + + private void onAccessibilityButtonClicked() { + getAccessibilityButtonController().dispatchAccessibilityButtonClicked(); + } + + private void onAccessibilityButtonAvailabilityChanged(boolean available) { + getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged( + available); + } + + /** + * 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. + * + * @param action The action to perform. + * @return Whether the action was successfully performed. + * + * @see #GLOBAL_ACTION_BACK + * @see #GLOBAL_ACTION_HOME + * @see #GLOBAL_ACTION_NOTIFICATIONS + * @see #GLOBAL_ACTION_RECENTS + */ + public final boolean performGlobalAction(int action) { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().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> + * + * @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().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().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; + 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().getConnection(mConnectionId); + if (mInfo != null && connection != null) { + try { + connection.setServiceInfo(mInfo); + mInfo = null; + AccessibilityInteractionClient.getInstance().clearCache(); + } 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); + } + return mWindowManager; + } + return super.getSystemService(name); + } + + /** + * Implement to return the implementation of the internal accessibility + * service interface. + */ + @Override + public final IBinder onBind(Intent intent) { + return new IAccessibilityServiceClientWrapper(this, getMainLooper(), 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. + final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE); + wm.setDefaultToken(windowToken); + } + + @Override + public boolean onGesture(int gestureId) { + return AccessibilityService.this.onGesture(gestureId); + } + + @Override + public boolean onKeyEvent(KeyEvent event) { + return AccessibilityService.this.onKeyEvent(event); + } + + @Override + public void onMagnificationChanged(int displayId, @NonNull Region region, + float scale, float centerX, float centerY) { + AccessibilityService.this.onMagnificationChanged(displayId, region, scale, + centerX, centerY); + } + + @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() { + AccessibilityService.this.onAccessibilityButtonClicked(); + } + + @Override + public void onAccessibilityButtonAvailabilityChanged(boolean available) { + AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available); + } + }); + } + + /** + * 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 + implements HandlerCaller.Callback { + private static final int DO_INIT = 1; + private static final int DO_ON_INTERRUPT = 2; + private static final int DO_ON_ACCESSIBILITY_EVENT = 3; + private static final int DO_ON_GESTURE = 4; + private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5; + private static final int DO_ON_KEY_EVENT = 6; + private static final int DO_ON_MAGNIFICATION_CHANGED = 7; + private static final int DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED = 8; + private static final int DO_GESTURE_COMPLETE = 9; + private static final int DO_ON_FINGERPRINT_ACTIVE_CHANGED = 10; + private static final int DO_ON_FINGERPRINT_GESTURE = 11; + private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12; + private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13; + + private final HandlerCaller mCaller; + + private final Callbacks mCallback; + + private int mConnectionId = AccessibilityInteractionClient.NO_ID; + + public IAccessibilityServiceClientWrapper(Context context, Looper looper, + Callbacks callback) { + mCallback = callback; + mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/); + } + + public void init(IAccessibilityServiceConnection connection, int connectionId, + IBinder windowToken) { + Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId, + connection, windowToken); + mCaller.sendMessage(message); + } + + public void onInterrupt() { + Message message = mCaller.obtainMessage(DO_ON_INTERRUPT); + mCaller.sendMessage(message); + } + + public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) { + Message message = mCaller.obtainMessageBO( + DO_ON_ACCESSIBILITY_EVENT, serviceWantsEvent, event); + mCaller.sendMessage(message); + } + + public void onGesture(int gestureId) { + Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId); + mCaller.sendMessage(message); + } + + public void clearAccessibilityCache() { + Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE); + mCaller.sendMessage(message); + } + + @Override + public void onKeyEvent(KeyEvent event, int sequence) { + Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event); + mCaller.sendMessage(message); + } + + /** Magnification changed callbacks for different displays */ + public void onMagnificationChanged(int displayId, @NonNull Region region, + float scale, float centerX, float centerY) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = region; + args.arg2 = scale; + args.arg3 = centerX; + args.arg4 = centerY; + args.argi1 = displayId; + + final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args); + mCaller.sendMessage(message); + } + + public void onSoftKeyboardShowModeChanged(int showMode) { + final Message message = + mCaller.obtainMessageI(DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED, showMode); + mCaller.sendMessage(message); + } + + public void onPerformGestureResult(int sequence, boolean successfully) { + Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence, + successfully ? 1 : 0); + mCaller.sendMessage(message); + } + + public void onFingerprintCapturingGesturesChanged(boolean active) { + mCaller.sendMessage(mCaller.obtainMessageI( + DO_ON_FINGERPRINT_ACTIVE_CHANGED, active ? 1 : 0)); + } + + public void onFingerprintGesture(int gesture) { + mCaller.sendMessage(mCaller.obtainMessageI(DO_ON_FINGERPRINT_GESTURE, gesture)); + } + + public void onAccessibilityButtonClicked() { + final Message message = mCaller.obtainMessage(DO_ACCESSIBILITY_BUTTON_CLICKED); + mCaller.sendMessage(message); + } + + public void onAccessibilityButtonAvailabilityChanged(boolean available) { + final Message message = mCaller.obtainMessageI( + DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED, (available ? 1 : 0)); + mCaller.sendMessage(message); + } + + @Override + public void executeMessage(Message message) { + switch (message.what) { + case DO_ON_ACCESSIBILITY_EVENT: { + AccessibilityEvent event = (AccessibilityEvent) message.obj; + boolean serviceWantsEvent = message.arg1 != 0; + if (event != null) { + // Send the event to AccessibilityCache via AccessibilityInteractionClient + AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); + if (serviceWantsEvent + && (mConnectionId != AccessibilityInteractionClient.NO_ID)) { + // Send the event to AccessibilityService + mCallback.onAccessibilityEvent(event); + } + // Make sure the event is recycled. + try { + event.recycle(); + } catch (IllegalStateException ise) { + /* ignore - best effort */ + } + } + } return; + + case DO_ON_INTERRUPT: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.onInterrupt(); + } + } return; + + case DO_INIT: { + mConnectionId = message.arg1; + SomeArgs args = (SomeArgs) message.obj; + IAccessibilityServiceConnection connection = + (IAccessibilityServiceConnection) args.arg1; + IBinder windowToken = (IBinder) args.arg2; + args.recycle(); + if (connection != null) { + AccessibilityInteractionClient.getInstance().addConnection(mConnectionId, + connection); + mCallback.init(mConnectionId, windowToken); + mCallback.onServiceConnected(); + } else { + AccessibilityInteractionClient.getInstance().removeConnection( + mConnectionId); + mConnectionId = AccessibilityInteractionClient.NO_ID; + AccessibilityInteractionClient.getInstance().clearCache(); + mCallback.init(AccessibilityInteractionClient.NO_ID, null); + } + } return; + + case DO_ON_GESTURE: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + final int gestureId = message.arg1; + mCallback.onGesture(gestureId); + } + } return; + + case DO_CLEAR_ACCESSIBILITY_CACHE: { + AccessibilityInteractionClient.getInstance().clearCache(); + } return; + + case DO_ON_KEY_EVENT: { + KeyEvent event = (KeyEvent) message.obj; + try { + IAccessibilityServiceConnection connection = AccessibilityInteractionClient + .getInstance().getConnection(mConnectionId); + if (connection != null) { + final boolean result = mCallback.onKeyEvent(event); + final int sequence = message.arg1; + 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; + + case DO_ON_MAGNIFICATION_CHANGED: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + final SomeArgs args = (SomeArgs) message.obj; + final Region region = (Region) args.arg1; + final float scale = (float) args.arg2; + final float centerX = (float) args.arg3; + final float centerY = (float) args.arg4; + final int displayId = args.argi1; + args.recycle(); + mCallback.onMagnificationChanged(displayId, region, scale, + centerX, centerY); + } + } return; + + case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + final int showMode = (int) message.arg1; + mCallback.onSoftKeyboardShowModeChanged(showMode); + } + } return; + + case DO_GESTURE_COMPLETE: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + final boolean successfully = message.arg2 == 1; + mCallback.onPerformGestureResult(message.arg1, successfully); + } + } return; + case DO_ON_FINGERPRINT_ACTIVE_CHANGED: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1); + } + } return; + case DO_ON_FINGERPRINT_GESTURE: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.onFingerprintGesture(message.arg1); + } + } return; + + case (DO_ACCESSIBILITY_BUTTON_CLICKED): { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.onAccessibilityButtonClicked(); + } + } return; + + case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + final boolean available = (message.arg1 != 0); + mCallback.onAccessibilityButtonAvailabilityChanged(available); + } + } return; + + default : + Log.w(LOG_TAG, "Unknown message type " + message.what); + } + } + } + + /** + * 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; + } + } +}
diff --git a/android/accessibilityservice/AccessibilityServiceInfo.java b/android/accessibilityservice/AccessibilityServiceInfo.java new file mode 100644 index 0000000..cf24b8e --- /dev/null +++ b/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,1240 @@ +/* + * 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.content.pm.PackageManager.FEATURE_FINGERPRINT; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.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.hardware.fingerprint.FingerprintManager; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.TypedValue; +import android.util.Xml; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.android.internal.R; + +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_canRequestEnhancedWebAccessibility + * @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_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_nonInteractiveUiTimeout + * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout + * @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; + + 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. + */ + 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; + + /** {@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> + * @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 + */ + public int flags; + + /** + * Whether or not the service has crashed and is awaiting restart. Only valid from {@link + * android.view.accessibility.AccessibilityManager#getEnabledAccessibilityServiceList(int)}, + * 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. + */ + 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; + + /** + * 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 description of the accessibility service. + */ + private int mDescriptionResId; + + /** + * Non localized description of the accessibility service. + */ + private String mNonLocalizedDescription; + + /** + * 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; + } + 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(); + } + } + 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 AccessibilitySerivice can change dynamically. + * + * @param other The info from which to update the properties. + * + * @hide + */ + public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) { + eventTypes = other.eventTypes; + packageNames = other.packageNames; + feedbackType = other.feedbackType; + notificationTimeout = other.notificationTimeout; + mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout; + mInteractiveUiTimeout = other.mInteractiveUiTimeout; + flags = other.flags; + } + + /** + * @hide + */ + public void setComponentName(ComponentName component) { + mComponentName = component; + } + + /** + * @hide + */ + public ComponentName getComponentName() { + return mComponentName; + } + + /** + * The accessibility service id. + * <p> + * <strong>Generated by the system.</strong> + * </p> + * @return The id. + */ + public String getId() { + return 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; + } + + /** + * 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 + */ + 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 + * + * @hide + */ + @UnsupportedAppUsage + public void setCapabilities(int capabilities) { + mCapabilities = capabilities; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + 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.writeString(mNonLocalizedDescription); + } + + 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()); + mResolveInfo = parcel.readParcelable(null); + mSettingsActivityName = parcel.readString(); + mCapabilities = parcel.readInt(); + mSummaryResId = parcel.readInt(); + mNonLocalizedSummary = parcel.readString(); + mDescriptionResId = parcel.readInt(); + mNonLocalizedDescription = parcel.readString(); + } + + @Override + public int hashCode() { + return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode()); + } + + @Override + public boolean equals(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("summary: ").append(mNonLocalizedSummary); + 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_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"; + 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_ENHANCED_WEB_ACCESSIBILITY: + return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; + 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"; + 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)); + 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/accessibilityservice/FingerprintGestureController.java b/android/accessibilityservice/FingerprintGestureController.java new file mode 100644 index 0000000..c30030d --- /dev/null +++ b/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/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java new file mode 100644 index 0000000..a3e7ad5 --- /dev/null +++ b/android/accessibilityservice/GestureDescription.java
@@ -0,0 +1,568 @@ +/* + * 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 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. + * <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 = 10; + + /** + * 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]; + + /** + * 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() {} + + private GestureDescription(List<StrokeDescription> strokes) { + mStrokes.addAll(strokes); + } + + /** + * 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); + } + + /** + * 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<>(); + + /** + * Add 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"); + } + + 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; + } + + public GestureDescription build() { + if (mStrokes.size() == 0) { + throw new IllegalStateException("Gestures must have at least one stroke"); + } + return new GestureDescription(mStrokes); + } + } + + /** + * 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()); + 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/accounts/AbstractAccountAuthenticator.java b/android/accounts/AbstractAccountAuthenticator.java new file mode 100644 index 0000000..25cd342 --- /dev/null +++ b/android/accounts/AbstractAccountAuthenticator.java
@@ -0,0 +1,997 @@ +/* + * 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.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Binder; +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_MANAGER_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. + * </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). + * The {@link AccountAuthenticatorActivity} handles this, so one may wish to extend that when + * writing activities to handle these requests. + */ +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 { + @Override + public void addAccount(IAccountAuthenticatorResponse response, String accountType, + String authTokenType, String[] features, Bundle options) + throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: accountType " + accountType + + ", authTokenType " + authTokenType + + ", features " + (features == null ? "[]" : Arrays.toString(features))); + } + checkBinderPermission(); + 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); + } + } + + @Override + public void confirmCredentials(IAccountAuthenticatorResponse response, + Account account, Bundle options) throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "confirmCredentials: " + account); + } + checkBinderPermission(); + 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); + } + } + + @Override + public void getAuthTokenLabel(IAccountAuthenticatorResponse response, + String authTokenType) + throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType); + } + checkBinderPermission(); + 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); + } + } + + @Override + public void getAuthToken(IAccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle loginOptions) + throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "getAuthToken: " + account + + ", authTokenType " + authTokenType); + } + checkBinderPermission(); + 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); + } + } + + @Override + public void updateCredentials(IAccountAuthenticatorResponse response, Account account, + String authTokenType, Bundle loginOptions) throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "updateCredentials: " + account + + ", authTokenType " + authTokenType); + } + checkBinderPermission(); + 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); + } + } + + @Override + public void editProperties(IAccountAuthenticatorResponse response, + String accountType) throws RemoteException { + checkBinderPermission(); + 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); + } + } + + @Override + public void hasFeatures(IAccountAuthenticatorResponse response, + Account account, String[] features) throws RemoteException { + checkBinderPermission(); + 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); + } + } + + @Override + public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response, + Account account) throws RemoteException { + checkBinderPermission(); + 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); + } + } + + @Override + public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, + Account account) throws RemoteException { + checkBinderPermission(); + 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); + } + } + + @Override + public void addAccountFromCredentials(IAccountAuthenticatorResponse response, + Account account, + Bundle accountCredentials) throws RemoteException { + checkBinderPermission(); + 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); + } + } + + @Override + public void startAddAccountSession(IAccountAuthenticatorResponse response, + String accountType, String authTokenType, String[] features, Bundle options) + throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "startAddAccountSession: accountType " + accountType + + ", authTokenType " + authTokenType + + ", features " + (features == null ? "[]" : Arrays.toString(features))); + } + checkBinderPermission(); + 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); + } + } + + @Override + public void startUpdateCredentialsSession( + IAccountAuthenticatorResponse response, + Account account, + String authTokenType, + Bundle loginOptions) throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "startUpdateCredentialsSession: " + + account + + ", authTokenType " + + authTokenType); + } + checkBinderPermission(); + 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); + + } + } + + @Override + public void finishSession( + IAccountAuthenticatorResponse response, + String accountType, + Bundle sessionBundle) throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "finishSession: accountType " + accountType); + } + checkBinderPermission(); + 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); + + } + } + + @Override + public void isCredentialsUpdateSuggested( + IAccountAuthenticatorResponse response, + Account account, + String statusToken) throws RemoteException { + checkBinderPermission(); + 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 void checkBinderPermission() { + final int uid = Binder.getCallingUid(); + final String perm = Manifest.permission.ACCOUNT_MANAGER; + if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("caller uid " + uid + " lacks " + perm); + } + } + + 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); + 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/accounts/Account.java b/android/accounts/Account.java new file mode 100644 index 0000000..c822d20 --- /dev/null +++ b/android/accounts/Account.java
@@ -0,0 +1,179 @@ +/* + * 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.annotation.UnsupportedAppUsage; +import android.content.Context; +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 + 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 + private final @Nullable String accessId; + + public boolean equals(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/accounts/AccountAndUser.java b/android/accounts/AccountAndUser.java new file mode 100644 index 0000000..b0d5343 --- /dev/null +++ b/android/accounts/AccountAndUser.java
@@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.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(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/accounts/AccountAuthenticatorActivity.java b/android/accounts/AccountAuthenticatorActivity.java new file mode 100644 index 0000000..967aa04 --- /dev/null +++ b/android/accounts/AccountAuthenticatorActivity.java
@@ -0,0 +1,82 @@ +/* + * 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. + */ +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); + + 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/accounts/AccountAuthenticatorResponse.java b/android/accounts/AccountAuthenticatorResponse.java new file mode 100644 index 0000000..bb2e327 --- /dev/null +++ b/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.annotation.UnsupportedAppUsage; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.Parcel; +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/accounts/AccountManager.java b/android/accounts/AccountManager.java new file mode 100644 index 0000000..26c2c0c --- /dev/null +++ b/android/accounts/AccountManager.java
@@ -0,0 +1,3346 @@ +/* + * 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.BroadcastBehavior; +import android.annotation.IntDef; +import android.annotation.NonNull; +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.UnsupportedAppUsage; +import android.app.Activity; +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.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.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.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"; + + @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 + 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) { + 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(); + } + } + + /** + * 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. + * + * @return An array of {@link AuthenticatorDescription} for every + * authenticator known to the AccountManager service. Empty (never + * null) if no authenticators are known. + */ + public AuthenticatorDescription[] getAuthenticatorTypes() { + try { + return mService.getAuthenticatorTypes(UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @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 AbstractAcccountAuthenticator managing the account did so or because the + * client shares a signature with the managing AbstractAccountAuthenticator. + * + * <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[] getAccounts() { + try { + return mService.getAccounts(null, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @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) { + try { + return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @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 AbstractAcccountAuthenticator 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> + * 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 AbstractAcccountAuthenticator + * 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. + */ + @NonNull + public Account[] getAccountsByType(String type) { + return getAccountsByTypeAsUser(type, mContext.getUser()); + } + + /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */ + @NonNull + @UnsupportedAppUsage + 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. + */ + public AccountManagerFuture<Boolean> hasFeatures(final Account account, + final String[] features, + AccountManagerCallback<Boolean> callback, Handler handler) { + 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, 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 AbstractAcccountAuthenticator + * 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); + } 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. + * + * @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); + } 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 + * @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. + */ + 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 + */ + @Deprecated + public AccountManagerFuture<Boolean> removeAccount(final Account account, + AccountManagerCallback<Boolean> callback, Handler handler) { + if (account == null) throw new IllegalArgumentException("account is null"); + return new Future2Task<Boolean>(handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.removeAccount(mResponse, account, false); + } + @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(); + } + + /** + * 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> + */ + public AccountManagerFuture<Bundle> removeAccount(final Account account, + final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { + if (account == null) throw new IllegalArgumentException("account is null"); + return new AmsTask(activity, handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.removeAccount(mResponse, account, activity != null); + } + }.start(); + } + + /** + * @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> + */ + 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 (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(); + } + + /** + * @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(); + } + + /** + * @hide + * Removes the shared account. + * @param account the account to remove + * @param user the user to remove the account from + * @return + */ + public boolean removeSharedAccount(final Account account, UserHandle user) { + try { + boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier()); + return val; + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * @hide + * @param user + * @return + */ + public Account[] getSharedAccounts(UserHandle user) { + try { + return mService.getSharedAccountsAsUser(user.getIdentifier()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * 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> + */ + 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 + 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); + 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 AbstractAcccountAuthenticator 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 AbstractAcccountAuthenticator 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 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> + * @see #startAddAccountSession and #startUpdateCredentialsSession + */ + 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(android.Manifest.permission.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(); + } + } +}
diff --git a/android/accounts/AccountManagerCallback.java b/android/accounts/AccountManagerCallback.java new file mode 100644 index 0000000..4aa7169 --- /dev/null +++ b/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/accounts/AccountManagerFuture.java b/android/accounts/AccountManagerFuture.java new file mode 100644 index 0000000..77670d9 --- /dev/null +++ b/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/accounts/AccountManagerInternal.java b/android/accounts/AccountManagerInternal.java new file mode 100644 index 0000000..68c17c3 --- /dev/null +++ b/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/accounts/AccountManagerPerfTest.java b/android/accounts/AccountManagerPerfTest.java new file mode 100644 index 0000000..e455e6a --- /dev/null +++ b/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/accounts/AccountManagerResponse.java b/android/accounts/AccountManagerResponse.java new file mode 100644 index 0000000..369a7c3 --- /dev/null +++ b/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/accounts/AccountsException.java b/android/accounts/AccountsException.java new file mode 100644 index 0000000..b997390 --- /dev/null +++ b/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/accounts/AuthenticatorDescription.java b/android/accounts/AuthenticatorDescription.java new file mode 100644 index 0000000..5556394 --- /dev/null +++ b/android/accounts/AuthenticatorDescription.java
@@ -0,0 +1,149 @@ +/* + * 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.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcelable; +import android.os.Parcel; + +/** + * 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(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/accounts/AuthenticatorException.java b/android/accounts/AuthenticatorException.java new file mode 100644 index 0000000..f778d7d --- /dev/null +++ b/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/accounts/CantAddAccountActivity.java b/android/accounts/CantAddAccountActivity.java new file mode 100644 index 0000000..f7f232e --- /dev/null +++ b/android/accounts/CantAddAccountActivity.java
@@ -0,0 +1,41 @@ +/* + * 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 android.app.Activity; +import android.os.Bundle; +import android.view.View; + +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); + } + + public void onCancelButtonClicked(View view) { + onBackPressed(); + } +}
diff --git a/android/accounts/ChooseAccountActivity.java b/android/accounts/ChooseAccountActivity.java new file mode 100644 index 0000000..4af22bf --- /dev/null +++ b/android/accounts/ChooseAccountActivity.java
@@ -0,0 +1,223 @@ +/* + * 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.app.ActivityManager; +import android.app.ActivityTaskManager; +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.IBinder; +import android.os.Parcelable; +import android.os.RemoteException; +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); + mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS); + mAccountManagerResponse = + getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE); + + // KEY_ACCOUNTS is a required parameter + if (mAccounts == null) { + setResult(RESULT_CANCELED); + finish(); + return; + } + + try { + IBinder activityToken = getActivityToken(); + mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken); + mCallingPackage = ActivityTaskManager.getService().getLaunchedFromPackage( + activityToken); + } catch (RemoteException re) { + // Couldn't figure out caller details + Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re); + } + + 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); + 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/accounts/ChooseAccountTypeActivity.java b/android/accounts/ChooseAccountTypeActivity.java new file mode 100644 index 0000000..e3352bc --- /dev/null +++ b/android/accounts/ChooseAccountTypeActivity.java
@@ -0,0 +1,203 @@ +/* + * 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); + + 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(); + } + 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/accounts/ChooseTypeAndAccountActivity.java b/android/accounts/ChooseTypeAndAccountActivity.java new file mode 100644 index 0000000..57c1083 --- /dev/null +++ b/android/accounts/ChooseTypeAndAccountActivity.java
@@ -0,0 +1,629 @@ +/* + * 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.ActivityTaskManager; +import com.google.android.collect.Sets; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcelable; +import android.os.RemoteException; +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 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 + ")"); + } + + String message = null; + + try { + IBinder activityToken = getActivityToken(); + mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken); + mCallingPackage = ActivityTaskManager.getService().getLaunchedFromPackage( + activityToken); + if (mCallingUid != 0 && mCallingPackage != null) { + Bundle restrictions = UserManager.get(this) + .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid))); + mDisallowAddAccounts = + restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false); + } + } catch (RemoteException re) { + // Couldn't figure out caller details + Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re); + } + + // 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); + if (selectedAccount != null) { + mSelectedAccountName = selectedAccount.name; + } + mAccounts = getAcceptableAccountChoices(AccountManager.get(this)); + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "selected account name is " + mSelectedAccountName); + } + + 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); + 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 + ", extras=" + extras + ")"); + } + + // 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); + if (intent != null) { + mPendingRequest = REQUEST_ADD_ACCOUNT; + mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage, + mCallingUid); + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(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); + 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 " + + accountName + ", " + accountType); + } + + 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 whitelisted 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/accounts/GrantCredentialsPermissionActivity.java b/android/accounts/GrantCredentialsPermissionActivity.java new file mode 100644 index 0000000..af74b03 --- /dev/null +++ b/android/accounts/GrantCredentialsPermissionActivity.java
@@ -0,0 +1,198 @@ +/* + * 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.res.Resources; +import android.os.Bundle; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.view.View; +import android.view.LayoutInflater; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.text.TextUtils; +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 Bundle mResultBundle = null; + protected LayoutInflater mInflater; + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + 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); + 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; + } + + 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); + 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/accounts/NetworkErrorException.java b/android/accounts/NetworkErrorException.java new file mode 100644 index 0000000..07f4ce9 --- /dev/null +++ b/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/accounts/OnAccountsUpdateListener.java b/android/accounts/OnAccountsUpdateListener.java new file mode 100644 index 0000000..2b4ee50 --- /dev/null +++ b/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/accounts/OperationCanceledException.java b/android/accounts/OperationCanceledException.java new file mode 100644 index 0000000..896d194 --- /dev/null +++ b/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/animation/AnimationHandler.java b/android/animation/AnimationHandler.java new file mode 100644 index 0000000..260323f --- /dev/null +++ b/android/animation/AnimationHandler.java
@@ -0,0 +1,317 @@ +/* + * 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.util.ArrayMap; +import android.view.Choreographer; + +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 { + /** + * Internal per-thread collections used to avoid set collisions as animations start and end + * while being processed. + * @hide + */ + 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; + + 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(); + } + + /** + * 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. + */ + 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/animation/AnimationThread.java b/android/animation/AnimationThread.java new file mode 100644 index 0000000..ce2aec7 --- /dev/null +++ b/android/animation/AnimationThread.java
@@ -0,0 +1,176 @@ +/* + * 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 com.android.ide.common.rendering.api.IAnimationListener; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; + +import android.os.Handler; +import android.os.Handler_Delegate; +import android.os.Message; + +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * Abstract animation thread. + * <p/> + * This does not actually start an animation, instead it fakes a looper that will play whatever + * animation is sending messages to its own {@link Handler}. + * <p/> + * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}. + * <p/> + * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do + * anything. + * + */ +public abstract class AnimationThread extends Thread { + + private static class MessageBundle implements Comparable<MessageBundle> { + final Handler mTarget; + final Message mMessage; + final long mUptimeMillis; + + MessageBundle(Handler target, Message message, long uptimeMillis) { + mTarget = target; + mMessage = message; + mUptimeMillis = uptimeMillis; + } + + @Override + public int compareTo(MessageBundle bundle) { + if (mUptimeMillis < bundle.mUptimeMillis) { + return -1; + } + return 1; + } + } + + private final RenderSessionImpl mSession; + + private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>(); + private final IAnimationListener mListener; + + public AnimationThread(RenderSessionImpl scene, String threadName, + IAnimationListener listener) { + super(threadName); + mSession = scene; + mListener = listener; + } + + public abstract Result preAnimation(); + public abstract void postAnimation(); + + @Override + public void run() { + Bridge.prepareThread(); + try { + /* FIXME: The ANIMATION_FRAME message no longer exists. Instead, the + * animation timing loop is completely based on a Choreographer objects + * that schedules animation and drawing frames. The animation handler is + * no longer even a handler; it is just a Runnable enqueued on the Choreographer. + Handler_Delegate.setCallback(new IHandlerCallback() { + @Override + public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { + if (msg.what == ValueAnimator.ANIMATION_START || + msg.what == ValueAnimator.ANIMATION_FRAME) { + mQueue.add(new MessageBundle(handler, msg, uptimeMillis)); + } else { + // just ignore. + } + } + }); + */ + + // call out to the pre-animation work, which should start an animation or more. + Result result = preAnimation(); + if (result.isSuccess() == false) { + mListener.done(result); + } + + // loop the animation + RenderSession session = mSession.getSession(); + do { + // check early. + if (mListener.isCanceled()) { + break; + } + + // get the next message. + MessageBundle bundle = mQueue.poll(); + if (bundle == null) { + break; + } + + // sleep enough for this bundle to be on time + long currentTime = System.currentTimeMillis(); + if (currentTime < bundle.mUptimeMillis) { + try { + sleep(bundle.mUptimeMillis - currentTime); + } catch (InterruptedException e) { + // FIXME log/do something/sleep again? + e.printStackTrace(); + } + } + + // check after sleeping. + if (mListener.isCanceled()) { + break; + } + + // ready to do the work, acquire the scene. + result = mSession.acquire(250); + if (result.isSuccess() == false) { + mListener.done(result); + return; + } + + // process the bundle. If the animation is not finished, this will enqueue + // the next message, so mQueue will have another one. + try { + // check after acquiring in case it took a while. + if (mListener.isCanceled()) { + break; + } + + bundle.mTarget.handleMessage(bundle.mMessage); + if (mSession.render(false /*freshRender*/).isSuccess()) { + mListener.onNewFrame(session); + } + } finally { + mSession.release(); + } + } while (mListener.isCanceled() == false && mQueue.size() > 0); + + mListener.done(Status.SUCCESS.createResult()); + + } catch (Throwable throwable) { + // can't use Bridge.getLog() as the exception might be thrown outside + // of an acquire/release block. + mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable)); + + } finally { + postAnimation(); + Handler_Delegate.setCallback(null); + Bridge.cleanupThread(); + } + } +}
diff --git a/android/animation/Animator.java b/android/animation/Animator.java new file mode 100644 index 0000000..17d54d2 --- /dev/null +++ b/android/animation/Animator.java
@@ -0,0 +1,678 @@ +/* + * 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.Nullable; +import android.annotation.UnsupportedAppUsage; +import android.content.pm.ActivityInfo.Config; +import android.content.res.ConstantState; + +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; + + /** + * 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() { + if (isStarted() && !mPaused) { + mPaused = true; + if (mPauseListeners != null) { + ArrayList<AnimatorPauseListener> tmpListeners = + (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationPause(this); + } + } + } + } + + /** + * 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; + if (mPauseListeners != null) { + ArrayList<AnimatorPauseListener> tmpListeners = + (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationResume(this); + } + } + } + } + + /** + * 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); + } + 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 + 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. + */ + void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {} + + /** + * <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(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(Animator animation, boolean isReverse) { + onAnimationEnd(animation); + } + + /** + * <p>Notifies the start of the animation.</p> + * + * @param animation The started animation. + */ + void onAnimationStart(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(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(Animator animation); + + /** + * <p>Notifies the repetition of the animation.</p> + * + * @param animation The animation which was repeated. + */ + void onAnimationRepeat(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(Animator animation); + + /** + * <p>Notifies that the animation was resumed, after being + * previously paused.</p> + * + * @param animation The animation being resumed. + * @see #resume() + */ + void onAnimationResume(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; + } + } +}
diff --git a/android/animation/AnimatorInflater.java b/android/animation/AnimatorInflater.java new file mode 100644 index 0000000..f69bbfd --- /dev/null +++ b/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/animation/AnimatorListenerAdapter.java b/android/animation/AnimatorListenerAdapter.java new file mode 100644 index 0000000..2ecb8c3 --- /dev/null +++ b/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/animation/AnimatorSet.java b/android/animation/AnimatorSet.java new file mode 100644 index 0000000..35cf39f --- /dev/null +++ b/android/animation/AnimatorSet.java
@@ -0,0 +1,2099 @@ +/* + * 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.view.animation.Animation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * 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; + + // This is to work around a bug in b/34736819. This needs to be removed once app team + // fixes their side. + private AnimatorListenerAdapter mDummyListener = 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()) { + ArrayList<AnimatorListener> tmpListeners = null; + if (mListeners != null) { + tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); + int size = tmpListeners.size(); + for (int i = 0; i < size; i++) { + tmpListeners.get(i).onAnimationCancel(this); + } + } + ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet); + int setSize = playingSet.size(); + for (int i = 0; i < setSize; i++) { + playingSet.get(i).mAnimation.cancel(); + } + mPlayingSet.clear(); + endAnimation(); + } + } + + // 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()) { + // 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; + while (mLastEventId > 0) { + mLastEventId = mLastEventId - 1; + AnimationEvent event = mEvents.get(mLastEventId); + 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 { + while (mLastEventId < mEvents.size() - 1) { + // Avoid potential reentrant loop caused by child animators manipulating + // AnimatorSet's lifecycle (i.e. not a recommended approach). + mLastEventId = mLastEventId + 1; + AnimationEvent event = mEvents.get(mLastEventId); + 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(); + } + } + } + mPlayingSet.clear(); + } + 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; + } + } + + @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); + } + } + } + + /** + * {@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"); + } + 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(); + } + + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this, 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) { + if (!isInitialized()) { + throw new UnsupportedOperationException("Children must be initialized."); + } + + // This makes sure the animation events are sorted an up to date. + initAnimation(); + + // 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--) { + if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + mEvents.get(i).mNode.mAnimation.skipToEndValue(true); + } + } + } else { + for (int i = 0; i < mEvents.size(); i++) { + if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) { + mEvents.get(i).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. + */ + @Override + void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { + if (currentPlayTime < 0 || lastPlayTime < 0) { + throw new UnsupportedOperationException("Error: Play time should never be negative."); + } + // TODO: take into account repeat counts and repeat callback when repeat is implemented. + // Clamp currentPlayTime and lastPlayTime + + // TODO: Make this more efficient + + // Convert the play times to the forward direction. + if (inReverse) { + if (getTotalDuration() == DURATION_INFINITE) { + throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite" + + " duration"); + } + long duration = getTotalDuration() - mStartDelay; + currentPlayTime = Math.min(currentPlayTime, duration); + currentPlayTime = duration - currentPlayTime; + lastPlayTime = duration - lastPlayTime; + inReverse = false; + } + + ArrayList<Node> unfinishedNodes = new ArrayList<>(); + // Assumes forward playing from here on. + for (int i = 0; i < mEvents.size(); i++) { + AnimationEvent event = mEvents.get(i); + if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) { + break; + } + + // This animation started prior to the current play time, and won't finish before the + // play time, add to the unfinished list. + if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + if (event.mNode.mEndTime == DURATION_INFINITE + || event.mNode.mEndTime > currentPlayTime) { + unfinishedNodes.add(event.mNode); + } + } + // For animations that do finish before the play time, end them in the sequence that + // they would in a normal run. + if (event.mEvent == AnimationEvent.ANIMATION_END) { + // Skip to the end of the animation. + event.mNode.mAnimation.skipToEndValue(false); + } + } + + // Seek unfinished animation to the right time. + for (int i = 0; i < unfinishedNodes.size(); i++) { + Node node = unfinishedNodes.get(i); + long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse); + if (!inReverse) { + playTime -= node.mAnimation.getStartDelay(); + } + node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse); + } + + // Seek not yet started animations. + for (int i = 0; i < mEvents.size(); i++) { + AnimationEvent event = mEvents.get(i); + if (event.getTime() > currentPlayTime + && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + event.mNode.mAnimation.skipToEndValue(true); + } + } + + } + + @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; + } + + private void skipToStartValue(boolean inReverse) { + skipToEndValue(!inReverse); + } + + /** + * 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. + * 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(); + + if (!isStarted() || isPaused()) { + if (mReversing) { + throw new UnsupportedOperationException("Error: Something went wrong. mReversing" + + " should not be set when AnimatorSet is not started."); + } + if (!mSeekState.isActive()) { + findLatestEventIdForTime(0); + // Set all the values to start values. + initChildren(); + mSeekState.setPlayTime(0, mReversing); + } + animateBasedOnPlayTime(playTime, 0, mReversing); + mSeekState.setPlayTime(playTime, mReversing); + } else { + // If the animation is running, just set the seek time and wait until the next frame + // (i.e. doAnimationFrame(...)) to advance the animation. + mSeekState.setPlayTime(playTime, mReversing); + } + } + + /** + * 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; + // Forcefully initialize all children based on their end time, so that if the start + // value of a child is dependent on a previous animation, the animation will be + // initialized after the the previous animations have been advanced to the end. + skipToEndValue(false); + } + } + + /** + * @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, getPlayTimeForNode(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, getPlayTimeForNode(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, getPlayTimeForNode(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 getPlayTimeForNode(long overallPlayTime, Node node) { + return getPlayTimeForNode(overallPlayTime, node, mReversing); + } + + private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) { + if (inReverse) { + overallPlayTime = getTotalDuration() - overallPlayTime; + return node.mEndTime - overallPlayTime; + } else { + return overallPlayTime - node.mStartTime; + } + } + + private void startAnimation() { + addDummyListener(); + + // 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) { + if (isInitialized()) { + skipToEndValue(!mReversing); + } else if (mReversing) { + // Reversing but haven't initialized all the children yet. + initChildren(); + skipToEndValue(!mReversing); + } else { + // If not all children are initialized and play direction is forward + for (int i = mEvents.size() - 1; i >= 0; i--) { + if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { + Animator anim = mEvents.get(i).mNode.mAnimation; + // Only reset the animations that have been initialized to start value, + // so that if they are defined without a start value, they will get the + // values set at the right time (i.e. the next animation run) + if (anim.isInitialized()) { + anim.skipToEndValue(true); + } + } + } + } + } + + 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 addDummyListener() { + for (int i = 1; i < mNodes.size(); i++) { + mNodes.get(i).mAnimation.addListener(mDummyListener); + } + } + + private void removeDummyListener() { + for (int i = 1; i < mNodes.size(); i++) { + mNodes.get(i).mAnimation.removeListener(mDummyListener); + } + } + + 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(); + // Call end listener + if (mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(this, mReversing); + } + } + removeDummyListener(); + 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.mDummyListener = 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(mDummyListener); + 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) { + // TODO: This can be simplified. + + // Clamp the play time + if (getTotalDuration() != DURATION_INFINITE) { + mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay); + } + 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 dummy ValueAnimator just to run the clock + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(delay); + after(anim); + return this; + } + + } + +}
diff --git a/android/animation/ArgbEvaluator.java b/android/animation/ArgbEvaluator.java new file mode 100644 index 0000000..5b69d18 --- /dev/null +++ b/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.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/animation/BidirectionalTypeConverter.java b/android/animation/BidirectionalTypeConverter.java new file mode 100644 index 0000000..960650e --- /dev/null +++ b/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/animation/FloatArrayEvaluator.java b/android/animation/FloatArrayEvaluator.java new file mode 100644 index 0000000..9ae1197 --- /dev/null +++ b/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/animation/FloatEvaluator.java b/android/animation/FloatEvaluator.java new file mode 100644 index 0000000..9463aa1 --- /dev/null +++ b/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 * (v1 - v0)</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/animation/FloatKeyframeSet.java b/android/animation/FloatKeyframeSet.java new file mode 100644 index 0000000..11837b5 --- /dev/null +++ b/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/animation/IntArrayEvaluator.java b/android/animation/IntArrayEvaluator.java new file mode 100644 index 0000000..d7f10f3 --- /dev/null +++ b/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/animation/IntEvaluator.java b/android/animation/IntEvaluator.java new file mode 100644 index 0000000..1de2ae7 --- /dev/null +++ b/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/animation/IntKeyframeSet.java b/android/animation/IntKeyframeSet.java new file mode 100644 index 0000000..f1e146e --- /dev/null +++ b/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/animation/Keyframe.java b/android/animation/Keyframe.java new file mode 100644 index 0000000..bcb94d1 --- /dev/null +++ b/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/animation/KeyframeSet.java b/android/animation/KeyframeSet.java new file mode 100644 index 0000000..116d063 --- /dev/null +++ b/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/animation/Keyframes.java b/android/animation/Keyframes.java new file mode 100644 index 0000000..6666294 --- /dev/null +++ b/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/animation/LayoutTransition.java b/android/animation/LayoutTransition.java new file mode 100644 index 0000000..c753710 --- /dev/null +++ b/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.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/animation/ObjectAnimator.java b/android/animation/ObjectAnimator.java new file mode 100644 index 0000000..1e1f155 --- /dev/null +++ b/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/animation/PathKeyframes.java b/android/animation/PathKeyframes.java new file mode 100644 index 0000000..b362904 --- /dev/null +++ b/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/animation/PointFEvaluator.java b/android/animation/PointFEvaluator.java new file mode 100644 index 0000000..91d501f --- /dev/null +++ b/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/animation/PropertyValuesHolder.java b/android/animation/PropertyValuesHolder.java new file mode 100644 index 0000000..76806a2 --- /dev/null +++ b/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/animation/PropertyValuesHolder_Accessor.java b/android/animation/PropertyValuesHolder_Accessor.java new file mode 100644 index 0000000..576d0ea --- /dev/null +++ b/android/animation/PropertyValuesHolder_Accessor.java
@@ -0,0 +1,40 @@ +/* + * 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.animation; + +import android.animation.PropertyValuesHolder.FloatPropertyValuesHolder; +import android.animation.PropertyValuesHolder.IntPropertyValuesHolder; +import android.animation.PropertyValuesHolder.MultiFloatValuesHolder; +import android.animation.PropertyValuesHolder.MultiIntValuesHolder; + +public class PropertyValuesHolder_Accessor { + /** + * Method used by layoutlib to ensure that {@link Class} instances are not cached by this + * class. Caching the {@link Class} instances will lead to leaking the {@link ClassLoader} + * used by that class. + * In layoutlib, these class loaders are instantiated dynamically to allow for new classes to + * be loaded when the user code changes. + */ + public static void clearClassCaches() { + PropertyValuesHolder.sGetterPropertyMap.clear(); + PropertyValuesHolder.sSetterPropertyMap.clear(); + IntPropertyValuesHolder.sJNISetterPropertyMap.clear(); + MultiIntValuesHolder.sJNISetterPropertyMap.clear(); + FloatPropertyValuesHolder.sJNISetterPropertyMap.clear(); + MultiFloatValuesHolder.sJNISetterPropertyMap.clear(); + } +}
diff --git a/android/animation/PropertyValuesHolder_Delegate.java b/android/animation/PropertyValuesHolder_Delegate.java new file mode 100644 index 0000000..1d7026c --- /dev/null +++ b/android/animation/PropertyValuesHolder_Delegate.java
@@ -0,0 +1,195 @@ +/* + * 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 com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Delegate implementing the native methods of android.animation.PropertyValuesHolder + * + * Through the layoutlib_create tool, the original native methods of PropertyValuesHolder have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + * The main goal of this class' methods are to provide a native way to access setters and getters + * on some object. We override these methods to use reflection since the original reflection + * implementation of the PropertyValuesHolder won't be able to access protected methods. + * + */ +/*package*/ +@SuppressWarnings("unused") +class PropertyValuesHolder_Delegate { + // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync + // 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 final Object sMethodIndexLock = new Object(); + private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>(); + private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>(); + private static long sNextId = 1; + + private static long registerMethod(Class<?> targetClass, String methodName, Class[] types, + int nArgs) { + // Encode the number of arguments in the method name + String methodIndexName = String.format("%1$s.%2$s#%3$d", targetClass.getSimpleName(), + methodName, nArgs); + synchronized (sMethodIndexLock) { + Long methodId = METHOD_NAME_TO_ID.get(methodIndexName); + + if (methodId != null) { + // The method was already registered + return methodId; + } + + Class[] args = new Class[nArgs]; + Method method = null; + for (Class typeVariant : types) { + for (int i = 0; i < nArgs; i++) { + args[i] = typeVariant; + } + try { + method = targetClass.getDeclaredMethod(methodName, args); + } catch (NoSuchMethodException ignore) { + } + } + + if (method != null) { + methodId = sNextId++; + ID_TO_METHOD.put(methodId, method); + METHOD_NAME_TO_ID.put(methodIndexName, methodId); + + return methodId; + } + } + + // Method not found + return 0; + } + + private static void callMethod(Object target, long methodID, Object... args) { + Method method = ID_TO_METHOD.get(methodID); + assert method != null; + + try { + method.setAccessible(true); + method.invoke(target, args); + } catch (IllegalAccessException | InvocationTargetException e) { + Bridge.getLog().error(null, "Unable to update property during animation", e, null); + } + } + + @LayoutlibDelegate + /*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) { + return nGetMultipleIntMethod(targetClass, methodName, 1); + } + + @LayoutlibDelegate + /*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) { + return nGetMultipleFloatMethod(targetClass, methodName, 1); + } + + @LayoutlibDelegate + /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName, + int numParams) { + return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams); + } + + @LayoutlibDelegate + /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName, + int numParams) { + return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams); + } + + @LayoutlibDelegate + /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) { + callMethod(target, methodID, arg); + } + + @LayoutlibDelegate + /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) { + callMethod(target, methodID, arg); + } + + @LayoutlibDelegate + /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1, + int arg2) { + callMethod(target, methodID, arg1, arg2); + } + + @LayoutlibDelegate + /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1, + int arg2, int arg3, int arg4) { + callMethod(target, methodID, arg1, arg2, arg3, arg4); + } + + @LayoutlibDelegate + /*package*/ static void nCallMultipleIntMethod(Object target, long methodID, + int[] args) { + assert args != null; + + // Box parameters + Object[] params = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = args; + } + callMethod(target, methodID, params); + } + + @LayoutlibDelegate + /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1, + float arg2) { + callMethod(target, methodID, arg1, arg2); + } + + @LayoutlibDelegate + /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1, + float arg2, float arg3, float arg4) { + callMethod(target, methodID, arg1, arg2, arg3, arg4); + } + + @LayoutlibDelegate + /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID, + float[] args) { + assert args != null; + + // Box parameters + Object[] params = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = args; + } + callMethod(target, methodID, params); + } +}
diff --git a/android/animation/RectEvaluator.java b/android/animation/RectEvaluator.java new file mode 100644 index 0000000..23eb766 --- /dev/null +++ b/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/animation/RevealAnimator.java b/android/animation/RevealAnimator.java new file mode 100644 index 0000000..0f85f49 --- /dev/null +++ b/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/animation/StateListAnimator.java b/android/animation/StateListAnimator.java new file mode 100644 index 0000000..b6d6910 --- /dev/null +++ b/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/animation/TimeAnimator.java b/android/animation/TimeAnimator.java new file mode 100644 index 0000000..113a21f --- /dev/null +++ b/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/animation/TimeInterpolator.java b/android/animation/TimeInterpolator.java new file mode 100644 index 0000000..0f5d8bf --- /dev/null +++ b/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/animation/TypeConverter.java b/android/animation/TypeConverter.java new file mode 100644 index 0000000..9ead2ad --- /dev/null +++ b/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/animation/TypeEvaluator.java b/android/animation/TypeEvaluator.java new file mode 100644 index 0000000..429c435 --- /dev/null +++ b/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/animation/ValueAnimator.java b/android/animation/ValueAnimator.java new file mode 100644 index 0000000..ebb03e7 --- /dev/null +++ b/android/animation/ValueAnimator.java
@@ -0,0 +1,1687 @@ +/* + * 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.IntDef; +import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Looper; +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.util.ArrayList; +import java.util.HashMap; + +/** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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(). + */ + private boolean mStartListenersCalled = 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; + + /** + * 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 + */ + @TestApi + public static void setDurationScale(float durationScale) { + sDurationScale = durationScale; + } + + /** + * @hide + */ + @TestApi + public static float getDurationScale() { + return sDurationScale; + } + + /** + * 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<String, PropertyValuesHolder>(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) { + 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); + } + } + + private void notifyStartListeners() { + if (mListeners != null && !mStartListenersCalled) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationStart(this, mReversing); + } + } + mStartListenersCalled = true; + } + + /** + * 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) && mListeners != null) { + if (!mRunning) { + // If it's not yet running, then start listeners weren't called. Call them now. + notifyStartListeners(); + } + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + for (AnimatorListener listener : tmpListeners) { + listener.onAnimationCancel(this); + } + } + 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(); + } + mRunning = false; + mStarted = false; + mStartListenersCalled = false; + mLastFrameTime = -1; + mFirstFrameTime = -1; + mStartTime = -1; + if (notify && mListeners != null) { + ArrayList<AnimatorListener> tmpListeners = + (ArrayList<AnimatorListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onAnimationEnd(this, 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; + } + if (mListeners != null) { + notifyStartListeners(); + } + } + + /** + * 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 + if (mListeners != null) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mListeners.get(i).onAnimationRepeat(this); + } + } + } 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 animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { + if (currentPlayTime < 0 || lastPlayTime < 0) { + throw new UnsupportedOperationException("Error: Play time should never be negative."); + } + + initAnimation(); + // Check whether repeat callback is needed only when repeat count is non-zero + if (mRepeatCount > 0) { + int iteration = (int) (currentPlayTime / mDuration); + int lastIteration = (int) (lastPlayTime / mDuration); + + // Clamp iteration to [0, mRepeatCount] + iteration = Math.min(iteration, mRepeatCount); + lastIteration = Math.min(lastIteration, mRepeatCount); + + if (iteration != lastIteration) { + if (mListeners != null) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mListeners.get(i).onAnimationRepeat(this); + } + } + } + } + + if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) { + skipToEndValue(inReverse); + } else { + // Find the current fraction: + float fraction = currentPlayTime / (float) mDuration; + fraction = getCurrentIterationFraction(fraction, inReverse); + animateValue(fraction); + } + } + + /** + * 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) { + fraction = mInterpolator.getInterpolation(fraction); + mCurrentFraction = fraction; + int numValues = mValues.length; + for (int i = 0; i < numValues; ++i) { + mValues[i].calculateValue(fraction); + } + if (mUpdateListeners != null) { + int numListeners = mUpdateListeners.size(); + for (int i = 0; i < numListeners; ++i) { + mUpdateListeners.get(i).onAnimationUpdate(this); + } + } + } + + @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.mStartListenersCalled = 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(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 AnimationHandler.getInstance(); + } +}
diff --git a/android/annotation/AnimRes.java b/android/annotation/AnimRes.java new file mode 100644 index 0000000..56f8acf --- /dev/null +++ b/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/annotation/AnimatorRes.java b/android/annotation/AnimatorRes.java new file mode 100644 index 0000000..cd4c189 --- /dev/null +++ b/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/annotation/AnyRes.java b/android/annotation/AnyRes.java new file mode 100644 index 0000000..44411a0 --- /dev/null +++ b/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/annotation/AnyThread.java b/android/annotation/AnyThread.java new file mode 100644 index 0000000..ee36a42 --- /dev/null +++ b/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/annotation/AppIdInt.java b/android/annotation/AppIdInt.java new file mode 100644 index 0000000..29838dd --- /dev/null +++ b/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/annotation/ArrayRes.java b/android/annotation/ArrayRes.java new file mode 100644 index 0000000..1407af1 --- /dev/null +++ b/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/annotation/AttrRes.java b/android/annotation/AttrRes.java new file mode 100644 index 0000000..285b80c --- /dev/null +++ b/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/annotation/BinderThread.java b/android/annotation/BinderThread.java new file mode 100644 index 0000000..ca5e14c --- /dev/null +++ b/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/annotation/BoolRes.java b/android/annotation/BoolRes.java new file mode 100644 index 0000000..f50785b --- /dev/null +++ b/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/annotation/BroadcastBehavior.java b/android/annotation/BroadcastBehavior.java new file mode 100644 index 0000000..70d82cb --- /dev/null +++ b/android/annotation/BroadcastBehavior.java
@@ -0,0 +1,61 @@ +/* + * 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 android.content.Intent; + +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 Intent#setPackage(String) + * @see Intent#setComponent(android.content.ComponentName) + */ + boolean explicitOnly() default false; + + /** + * This broadcast will only be delivered to registered receivers. + * + * @see Intent#FLAG_RECEIVER_REGISTERED_ONLY + */ + boolean registeredOnly() default false; + + /** + * This broadcast will include all {@code AndroidManifest.xml} receivers + * regardless of process state. + * + * @see 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/annotation/BytesLong.java b/android/annotation/BytesLong.java new file mode 100644 index 0000000..f5e1a9c --- /dev/null +++ b/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/annotation/CallSuper.java b/android/annotation/CallSuper.java new file mode 100644 index 0000000..c16b511 --- /dev/null +++ b/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/annotation/CallbackExecutor.java b/android/annotation/CallbackExecutor.java new file mode 100644 index 0000000..5671a3d --- /dev/null +++ b/android/annotation/CallbackExecutor.java
@@ -0,0 +1,41 @@ +/* + * 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 android.content.Context; +import android.os.AsyncTask; + +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 Context#getMainExecutor()}. To + * dispatch events through a shared thread pool, you can use + * {@link AsyncTask#THREAD_POOL_EXECUTOR}. + * @hide + */ +@Retention(SOURCE) +@Target(PARAMETER) +public @interface CallbackExecutor { +}
diff --git a/android/annotation/CheckResult.java b/android/annotation/CheckResult.java new file mode 100644 index 0000000..97d031a --- /dev/null +++ b/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/annotation/ColorInt.java b/android/annotation/ColorInt.java new file mode 100644 index 0000000..4671b1b --- /dev/null +++ b/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/annotation/ColorLong.java b/android/annotation/ColorLong.java new file mode 100644 index 0000000..9b19c76 --- /dev/null +++ b/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/annotation/ColorRes.java b/android/annotation/ColorRes.java new file mode 100644 index 0000000..061faa0 --- /dev/null +++ b/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/annotation/Condemned.java b/android/annotation/Condemned.java new file mode 100644 index 0000000..186409b --- /dev/null +++ b/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/annotation/CurrentTimeMillisLong.java b/android/annotation/CurrentTimeMillisLong.java new file mode 100644 index 0000000..355bb5a --- /dev/null +++ b/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/annotation/CurrentTimeSecondsLong.java b/android/annotation/CurrentTimeSecondsLong.java new file mode 100644 index 0000000..2b4ffd7 --- /dev/null +++ b/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/annotation/DimenRes.java b/android/annotation/DimenRes.java new file mode 100644 index 0000000..02ae00c --- /dev/null +++ b/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/annotation/Dimension.java b/android/annotation/Dimension.java new file mode 100644 index 0000000..5f705ad --- /dev/null +++ b/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/annotation/DrawableRes.java b/android/annotation/DrawableRes.java new file mode 100644 index 0000000..ebefa1d --- /dev/null +++ b/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/annotation/DurationMillisLong.java b/android/annotation/DurationMillisLong.java new file mode 100644 index 0000000..ce77532 --- /dev/null +++ b/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/annotation/ElapsedRealtimeLong.java b/android/annotation/ElapsedRealtimeLong.java new file mode 100644 index 0000000..f77ff72 --- /dev/null +++ b/android/annotation/ElapsedRealtimeLong.java
@@ -0,0 +1,41 @@ +/* + * 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 android.os.SystemClock; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc Value is a non-negative timestamp in the + * {@link SystemClock#elapsedRealtime()} time base. + * @paramDoc Value is a non-negative timestamp in the + * {@link SystemClock#elapsedRealtime()} time base. + * @returnDoc Value is a non-negative timestamp in the + * {@link SystemClock#elapsedRealtime()} time base. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface ElapsedRealtimeLong { +}
diff --git a/android/annotation/FloatRange.java b/android/annotation/FloatRange.java new file mode 100644 index 0000000..05b5168 --- /dev/null +++ b/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/annotation/FontRes.java b/android/annotation/FontRes.java new file mode 100644 index 0000000..dbacb58 --- /dev/null +++ b/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/annotation/FractionRes.java b/android/annotation/FractionRes.java new file mode 100644 index 0000000..fd84d3e --- /dev/null +++ b/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/annotation/HalfFloat.java b/android/annotation/HalfFloat.java new file mode 100644 index 0000000..256008c --- /dev/null +++ b/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/annotation/IdRes.java b/android/annotation/IdRes.java new file mode 100644 index 0000000..b286965 --- /dev/null +++ b/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/annotation/IntDef.java b/android/annotation/IntDef.java new file mode 100644 index 0000000..f84a676 --- /dev/null +++ b/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/annotation/IntRange.java b/android/annotation/IntRange.java new file mode 100644 index 0000000..c043e2d --- /dev/null +++ b/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/annotation/IntegerRes.java b/android/annotation/IntegerRes.java new file mode 100644 index 0000000..5313f4a --- /dev/null +++ b/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/annotation/InterpolatorRes.java b/android/annotation/InterpolatorRes.java new file mode 100644 index 0000000..8877a5f --- /dev/null +++ b/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/annotation/LayoutRes.java b/android/annotation/LayoutRes.java new file mode 100644 index 0000000..15ba86f --- /dev/null +++ b/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/annotation/LongDef.java b/android/annotation/LongDef.java new file mode 100644 index 0000000..8723eef --- /dev/null +++ b/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/annotation/MainThread.java b/android/annotation/MainThread.java new file mode 100644 index 0000000..556fdb4 --- /dev/null +++ b/android/annotation/MainThread.java
@@ -0,0 +1,50 @@ +/* + * 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 android.os.Looper; + +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 + * {@linkplain Looper#getMainLooper() main thread} of your app. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) +public @interface MainThread { +}
diff --git a/android/annotation/MenuRes.java b/android/annotation/MenuRes.java new file mode 100644 index 0000000..b6dcc46 --- /dev/null +++ b/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/annotation/NavigationRes.java b/android/annotation/NavigationRes.java new file mode 100644 index 0000000..3af5ecf --- /dev/null +++ b/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/annotation/NonNull.java b/android/annotation/NonNull.java new file mode 100644 index 0000000..927f997 --- /dev/null +++ b/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 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 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 must never be {@code null}. + * @returnDoc This value will never be {@code null}. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface NonNull { +}
diff --git a/android/annotation/Nullable.java b/android/annotation/Nullable.java new file mode 100644 index 0000000..b60170b --- /dev/null +++ b/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 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 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/annotation/PluralsRes.java b/android/annotation/PluralsRes.java new file mode 100644 index 0000000..31ac729 --- /dev/null +++ b/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/annotation/Px.java b/android/annotation/Px.java new file mode 100644 index 0000000..cec7f80 --- /dev/null +++ b/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/annotation/RawRes.java b/android/annotation/RawRes.java new file mode 100644 index 0000000..39970b3 --- /dev/null +++ b/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/annotation/RequiresFeature.java b/android/annotation/RequiresFeature.java new file mode 100644 index 0000000..fc93f03 --- /dev/null +++ b/android/annotation/RequiresFeature.java
@@ -0,0 +1,45 @@ +/* + * 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 android.content.pm.PackageManager; + +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. + * + * @see PackageManager#hasSystemFeature(String) + * @hide + */ +@Retention(SOURCE) +@Target({TYPE,FIELD,METHOD,CONSTRUCTOR}) +public @interface RequiresFeature { + /** + * The name of the device feature that is required. + * + * @see PackageManager#hasSystemFeature(String) + */ + String value(); +}
diff --git a/android/annotation/RequiresPermission.java b/android/annotation/RequiresPermission.java new file mode 100644 index 0000000..59d419f --- /dev/null +++ b/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 android.content.Intent; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +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.SOURCE; + +/** + * 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(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> + * + * @hide + */ +@Retention(SOURCE) +@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/annotation/SdkConstant.java b/android/annotation/SdkConstant.java new file mode 100644 index 0000000..0a53186 --- /dev/null +++ b/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/annotation/Size.java b/android/annotation/Size.java new file mode 100644 index 0000000..7c3e70f --- /dev/null +++ b/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/annotation/StringDef.java b/android/annotation/StringDef.java new file mode 100644 index 0000000..a37535b --- /dev/null +++ b/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/annotation/StringRes.java b/android/annotation/StringRes.java new file mode 100644 index 0000000..190b68a --- /dev/null +++ b/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/annotation/StyleRes.java b/android/annotation/StyleRes.java new file mode 100644 index 0000000..4453b8d --- /dev/null +++ b/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/annotation/StyleableRes.java b/android/annotation/StyleableRes.java new file mode 100644 index 0000000..3c1895e --- /dev/null +++ b/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/annotation/SuppressAutoDoc.java b/android/annotation/SuppressAutoDoc.java new file mode 100644 index 0000000..e34e03b --- /dev/null +++ b/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/annotation/SuppressLint.java b/android/annotation/SuppressLint.java new file mode 100644 index 0000000..2d3456b --- /dev/null +++ b/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/annotation/SystemApi.java b/android/annotation/SystemApi.java new file mode 100644 index 0000000..e96ff01 --- /dev/null +++ b/android/annotation/SystemApi.java
@@ -0,0 +1,44 @@ +/* + * 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 { +}
diff --git a/android/annotation/SystemService.java b/android/annotation/SystemService.java new file mode 100644 index 0000000..0c5d15e --- /dev/null +++ b/android/annotation/SystemService.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.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.content.Context; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Description of a system service available through + * {@link 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 Context#getSystemService(String)}. + * + * @see Context#getSystemServiceName(Class) + */ + String value(); +}
diff --git a/android/annotation/TargetApi.java b/android/annotation/TargetApi.java new file mode 100644 index 0000000..975318e --- /dev/null +++ b/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/annotation/TestApi.java b/android/annotation/TestApi.java new file mode 100644 index 0000000..0e9ed37 --- /dev/null +++ b/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/annotation/TransitionRes.java b/android/annotation/TransitionRes.java new file mode 100644 index 0000000..06bac74 --- /dev/null +++ b/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/annotation/UiThread.java b/android/annotation/UiThread.java new file mode 100644 index 0000000..6d7eedc --- /dev/null +++ b/android/annotation/UiThread.java
@@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.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 android.os.Looper; + +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 Looper#getMainLooper() main thread} of your app. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER}) +public @interface UiThread { +}
diff --git a/android/annotation/UnsupportedAppUsage.java b/android/annotation/UnsupportedAppUsage.java new file mode 100644 index 0000000..ac3daaf --- /dev/null +++ b/android/annotation/UnsupportedAppUsage.java
@@ -0,0 +1,135 @@ +/* + * 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.CLASS; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that this non-SDK interface is used by apps. A non-SDK interface is a + * class member (field or method) that is not part of the public SDK. Since the + * member is not part of the SDK, usage by apps is not supported. + * + * <h2>If you are an Android App developer</h2> + * + * This annotation indicates that you may be able to access the member, but that + * this access is discouraged and not supported by Android. If there is a value + * for {@link #maxTargetSdk()} on the annotation, access will be restricted based + * on the {@code targetSdkVersion} value set in your manifest. + * + * <p>Fields and methods annotated with this are likely to be restricted, changed + * or removed in future Android releases. If you rely on these members for + * functionality that is not otherwise supported by Android, consider filing a + * <a href="http://g.co/dev/appcompat">feature request</a>. + * + * <h2>If you are an Android OS developer</h2> + * + * This annotation acts as a heads up that changing a given method or field + * may affect apps, potentially breaking them when the next Android version is + * released. In some cases, for members that are heavily used, this annotation + * may imply restrictions on changes to the member. + * + * <p>This annotation also results in access to the member being permitted by the + * runtime, with a warning being generated in debug builds. Which apps can access + * the member is determined by the value of {@link #maxTargetSdk()}. + * + * <p>For more details, see go/UnsupportedAppUsage. + * + * {@hide} + */ +@Retention(CLASS) +@Target({CONSTRUCTOR, METHOD, FIELD, TYPE}) +@Repeatable(UnsupportedAppUsage.Container.class) +public @interface UnsupportedAppUsage { + + /** + * Associates a bug tracking the work to add a public alternative to this API. Optional. + * + * @return ID of the associated tracking bug + */ + long trackingBug() default 0; + + /** + * Indicates that usage of this API is limited to apps based on their target SDK version. + * + * <p>Access to the API is allowed if the targetSdkVersion in the apps manifest is no greater + * than this value. Access checks are performed at runtime. + * + * <p>This is used to give app developers a grace period to migrate off a non-SDK interface. + * When making Android version N, existing APIs can have a maxTargetSdk of N-1 added to them. + * Developers must then migrate off the API when their app is updated in future, but it will + * continue working in the meantime. + * + * <p>Possible values are: + * <ul> + * <li> + * {@link android.os.Build.VERSION_CODES#O} or {@link android.os.Build.VERSION_CODES#P}, + * to limit access to apps targeting these SDKs (or earlier). + * </li> + * <li> + * absent (default value) - All apps can access this API, but doing so may result in + * warnings in the log, UI warnings (on developer builds) and/or strictmode violations. + * The API is likely to be further restricted in future. + * </li> + * + * </ul> + * + * Note, if this is set to {@link android.os.Build.VERSION_CODES#O}, apps targeting O + * maintenance releases will also be allowed to use the API, and similarly for any future + * maintenance releases of P. + * + * @return The maximum value for an apps targetSdkVersion in order to access this API. + */ + int maxTargetSdk() default Integer.MAX_VALUE; + + /** + * For debug use only. The expected dex signature to be generated for this API, used to verify + * parts of the build process. + * + * @return A dex API signature. + */ + String expectedSignature() default ""; + + /** + * The signature of an implicit (not present in the source) member that forms part of the + * hiddenapi. + * + * <p>Allows access to non-SDK API elements that are not represented in the input source to be + * managed. + * + * <p>This must only be used when applying the annotation to a type, using it in any other + * situation is an error. + * + * @return A dex API signature. + */ + String implicitMember() default ""; + + /** + * Container for {@link UnsupportedAppUsage} that allows it to be applied repeatedly to types. + */ + @Retention(CLASS) + @Target(TYPE) + @interface Container { + UnsupportedAppUsage[] value(); + } +}
diff --git a/android/annotation/UserIdInt.java b/android/annotation/UserIdInt.java new file mode 100644 index 0000000..7b9ce25 --- /dev/null +++ b/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/annotation/Widget.java b/android/annotation/Widget.java new file mode 100644 index 0000000..6756cd7 --- /dev/null +++ b/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/annotation/WorkerThread.java b/android/annotation/WorkerThread.java new file mode 100644 index 0000000..8c2a4d3 --- /dev/null +++ b/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/annotation/XmlRes.java b/android/annotation/XmlRes.java new file mode 100644 index 0000000..5fb8a4a --- /dev/null +++ b/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/app/ActionBar.java b/android/app/ActionBar.java new file mode 100644 index 0000000..e573279 --- /dev/null +++ b/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.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/app/Activity.java b/android/app/Activity.java new file mode 100644 index 0000000..dc52c52 --- /dev/null +++ b/android/app/Activity.java
@@ -0,0 +1,8676 @@ +/* + * 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.os.Process.myUid; + +import static java.lang.Character.MIN_VALUE; + +import android.annotation.CallSuper; +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.SystemApi; +import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; +import android.app.VoiceInteractor.Request; +import android.app.admin.DevicePolicyManager; +import android.app.assist.AssistContent; +import android.content.ComponentCallbacks2; +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.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.Rect; +import android.graphics.drawable.Drawable; +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.Parcelable; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.StrictMode; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +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.EventLog; +import android.util.Log; +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.AutofillId; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillManager.AutofillClient; +import android.view.autofill.AutofillPopupWindow; +import android.view.autofill.IAutofillWindowPresenter; +import android.view.contentcapture.ContentCaptureManager; +import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; +import android.widget.AdapterView; +import android.widget.Toast; +import android.widget.Toolbar; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +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 dalvik.system.VMRuntime; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.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 android.support.v4.app.FragmentActivity} subclass + * can make use of the {@link android.support.v4.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 + * in 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); + * + * SharedPreferences mPrefs = getSharedPreferences(); + * 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. + */ +public class Activity extends ContextThemeWrapper + implements LayoutInflater.Factory2, + Window.Callback, KeyEvent.Callback, + OnCreateContextMenuListener, ComponentCallbacks2, + Window.OnWindowDismissedCallback, WindowControllerCallback, + AutofillManager.AutofillClient, 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 + static final String FRAGMENTS_TAG = "android:fragments"; + private static final String LAST_AUTOFILL_ID = "android:lastAutofillId"; + + private static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded"; + 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 AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:"; + + 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 static class ManagedDialog { + Dialog mDialog; + Bundle mArgs; + } + 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; + @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 + 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; + private SearchManager mSearchManager; + private MenuInflater mMenuInflater; + + /** The autofill manager. Always access via {@link #getAutofillManager()}. */ + @Nullable private AutofillManager mAutofillManager; + + /** 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()); + + 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; + + private boolean mAutoFillResetNeeded; + private boolean mAutoFillIgnoreFirstResumePause; + + /** The last autofill id that was returned from {@link #getNextAutofillId()} */ + private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID; + + private AutofillPopupWindow mAutofillPopupWindow; + + /** @hide */ + boolean mEnterAnimationComplete; + + 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; + } + + /** 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 android.support.v4.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 autofill manager + * + * @return The autofill manager + */ + @NonNull private AutofillManager getAutofillManager() { + if (mAutofillManager == null) { + mAutofillManager = getSystemService(AutofillManager.class); + } + + return mAutofillManager; + } + + /** + * (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, 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); + } + } + + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + if (newBase != null) { + newBase.setAutofillClient(this); + newBase.setContentCaptureOptions(getContentCaptureOptions()); + } + } + + /** @hide */ + @Override + public final AutofillClient getAutofillClient() { + return this; + } + + /** @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); + } + } + + 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 Object[] collectActivityLifecycleCallbacks() { + Object[] callbacks = null; + synchronized (mActivityLifecycleCallbacks) { + if (mActivityLifecycleCallbacks.size() > 0) { + callbacks = mActivityLifecycleCallbacks.toArray(); + } + } + return callbacks; + } + + /** + * 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) { + mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false); + mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID, + View.LAST_APP_AUTOFILL_ID); + + if (mAutoFillResetNeeded) { + getAutofillManager().onCreate(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; + + } + + /** + * 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}. + * + * @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); + } + + /** + * 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(); + + if (mAutoFillResetNeeded) { + getAutofillManager().onVisibleForAutofill(); + } + } + + /** + * 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}, for your activity to start interacting with the user. This is an indicator + * that the activity became active and ready to receive input. It is on top of an activity stack + * and visible to user. + * + * <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); + enableAutofillCompatibilityIfNeeded(); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest# + // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial + // window visibility after recreation is INVISIBLE in onResume() and next frame + // ViewRootImpl.performTraversals() changes window visibility to VISIBLE. + // So we cannot call View.notifyEnterOrExited() which will do nothing + // when View.isVisibleToUser() is false. + getAutofillManager().notifyViewEntered(focus); + } + } + } + + 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); + 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); + + writeEventLog(isTopResumedActivity + ? LOG_AM_ON_TOP_RESUMED_GAINED_CALLED : LOG_AM_ON_TOP_RESUMED_LOST_CALLED, 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()); + } + } + + /** + * Gets the next autofill ID. + * + * <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned + * will be unique. + * + * @return A ID that is unique in the activity + * + * {@hide} + */ + @Override + public int getNextAutofillId() { + if (mLastAutofillId == Integer.MAX_VALUE - 1) { + mLastAutofillId = View.LAST_APP_AUTOFILL_ID; + } + + mLastAutofillId++; + + return mLastAutofillId; + } + + /** + * @hide + */ + @Override + public AutofillId autofillClientGetNextAutofillId() { + return new AutofillId(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 true if this is also the root + * of a voice interaction. That is, returns 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 false. + */ + public boolean isVoiceInteractionRoot() { + try { + return mVoiceInteractor != null + && ActivityTaskManager.getService().isRootVoiceInteraction(mToken); + } catch (RemoteException e) { + } + return false; + } + + /** + * 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) { + try { + ActivityTaskManager.getService().startLocalVoiceInteraction(mToken, privateOptions); + } catch (RemoteException re) { + } + } + + /** + * 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() { + try { + ActivityTaskManager.getService().stopLocalVoiceInteraction(mToken); + } catch (RemoteException re) { + } + } + + /** + * 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()); + + outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId); + Parcelable p = mFragments.saveAllState(); + if (p != null) { + outState.putParcelable(FRAGMENTS_TAG, p); + } + if (mAutoFillResetNeeded) { + outState.putBoolean(AUTOFILL_RESET_NEEDED, true); + getAutofillManager().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(); + if (mAutoFillResetNeeded) { + if (!mAutoFillIgnoreFirstResumePause) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill notifyViewExited " + this); + View focus = getCurrentFocus(); + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { + getAutofillManager().notifyViewExited(focus); + } + } else { + // reset after first pause() + if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill got first pause " + this); + mAutoFillIgnoreFirstResumePause = false; + } + } + + notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_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 an 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()} is being called and + * before {@link #onStop()} is being called. + * + * <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() { + Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS); + intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME); + sendBroadcastAsUser(intent, Process.myUserHandle()); + } + + /** + * Dismiss the Keyboard Shortcuts screen. + */ + public final void dismissKeyboardShortcutsHelper() { + Intent intent = new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS); + intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME); + 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) { + try { + return ActivityTaskManager.getService().showAssistFromActivity(mToken, args); + } catch (RemoteException e) { + } + return false; + } + + /** + * 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(); + dispatchActivityStopped(); + mTranslucentCallback = null; + mCalled = true; + + if (mAutoFillResetNeeded) { + getAutofillManager().onInvisibleForAutofill(); + } + + if (isFinishing()) { + if (mAutoFillResetNeeded) { + getAutofillManager().onActivityFinishing(); + } else if (mIntent != null + && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) { + // Activity was launched when user tapped a link in the Autofill Save UI - since + // user launched another activity, the Save UI should not be restored when this + // activity is finished. + getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL, + mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)); + } + } + mEnterAnimationComplete = false; + } + + /** + * 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; + + // 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); + } + + /** + * Report to the system that your app is now fully drawn, purely for diagnostic + * purposes (calling it does not impact the visible behavior of the activity). + * This is only 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. + */ + public void reportFullyDrawn() { + if (mDoReportFullyDrawn) { + mDoReportFullyDrawn = false; + try { + ActivityTaskManager.getService().reportActivityFullyDrawn( + mToken, mRestoredFromBundle); + VMRuntime.getRuntime().notifyStartupCompleted(); + } catch (RemoteException e) { + } + } + } + + /** + * 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() { + try { + return ActivityTaskManager.getService().isInMultiWindowMode(mToken); + } catch (RemoteException e) { + } + return false; + } + + /** + * 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 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() { + try { + return ActivityTaskManager.getService().isInPictureInPictureMode(mToken); + } catch (RemoteException e) { + } + return false; + } + + /** + * 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()); + } + + /** @removed */ + @Deprecated + public boolean enterPictureInPictureMode(@NonNull PictureInPictureArgs args) { + return enterPictureInPictureMode(PictureInPictureArgs.convert(args)); + } + + /** + * 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. + * + * @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) { + try { + 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"); + } + return ActivityTaskManager.getService().enterPictureInPictureMode(mToken, params); + } catch (RemoteException e) { + return false; + } + } + + /** @removed */ + @Deprecated + public void setPictureInPictureArgs(@NonNull PictureInPictureArgs args) { + setPictureInPictureParams(PictureInPictureArgs.convert(args)); + } + + /** + * 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) { + try { + if (!deviceSupportsPictureInPictureMode()) { + return; + } + if (params == null) { + throw new IllegalArgumentException("Expected non-null picture-in-picture params"); + } + ActivityTaskManager.getService().setPictureInPictureParams(mToken, params); + } catch (RemoteException e) { + } + } + + /** + * 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 larger than three. + */ + public int getMaxNumPictureInPictureActions() { + try { + return ActivityTaskManager.getService().getMaxNumPictureInPictureActions(mToken); + } catch (RemoteException e) { + return 0; + } + } + + /** + * @return Whether this device supports picture-in-picture. + */ + private boolean deviceSupportsPictureInPictureMode() { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + + 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 + */ + @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); + } + } + + /** + * 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(); + } + + public void onTrimMemory(int level) { + if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level); + mCalled = true; + mFragments.dispatchTrimMemory(level); + } + + /** + * Return the FragmentManager for interacting with fragments associated + * with this activity. + * + * @deprecated Use {@link android.support.v4.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 + * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.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 + 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() + * @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) { + if (getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.ECLAIR) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() + && !event.isCanceled()) { + 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; + } + + /** + * Called when the activity has detected the user's press of the back + * key. The default implementation simply finishes the current activity, + * but you can override this to do whatever you want. + */ + public void onBackPressed() { + if (mActionBar != null && mActionBar.collapseActionView()) { + return; + } + + FragmentManager fragmentManager = mFragments.getFragmentManager(); + + if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) { + return; + } + if (!isTaskRoot()) { + // If the activity is not the root of the task, allow finish to proceed normally. + finishAfterTransition(); + return; + } + try { + // Inform activity task manager that the activity received a back press + // while at the root of the task. This call allows ActivityTaskManager + // to intercept or defer finishing. + ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken, + new IRequestFinishCallback.Stub() { + public void requestFinish() { + mHandler.post(() -> finishAfterTransition()); + } + }); + } catch (RemoteException e) { + finishAfterTransition(); + } + } + + /** + * 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 + * under it. 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. + * The default implementation always returns false. + */ + 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, mouse hovers, track pad + * touches, scroll wheel movements and other input events. 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. + * The following code example shows how this is done. + * </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); + } + } + + + /** + * Moves the activity between {@link WindowConfiguration#WINDOWING_MODE_FREEFORM} windowing mode + * and {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. + * + * @hide + */ + @Override + public void toggleFreeformWindowingMode() throws RemoteException { + ActivityTaskManager.getService().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(); + } + } + + /** + * 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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); + } + + 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> + * 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> + * Calling this API for permissions already granted to your app would show UI + * to the user to decide whether the app can still hold these permissions. This + * can be useful if the way your app uses data guarded by the permissions + * changes significantly. + * </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/googlesamples/android-RuntimePermissions"> + * RuntimePermissions</a> sample app demonstrates 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; + } + 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 for requesting a permission. + * You should do this only if you do not have the permission and the context in + * which the permission is requested does not clearly communicate to the user + * what would be the benefit from granting this permission. + * <p> + * For example, if you write a camera app, requesting the camera permission + * would be expected by the user and no rationale for why it is requested is + * needed. If however, the app needs location for tagging photos then a non-tech + * savvy user may wonder how location is related to taking photos. In this case + * you may choose to show UI with rationale of requesting this permission. + * </p> + * + * @param permission A permission your app wants to request. + * @return Whether you can 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(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; + } + + /** + * @hide Implement to provide correct calling token. + */ + @UnsupportedAppUsage + public void startActivityForResultAsUser(Intent intent, int requestCode, UserHandle user) { + startActivityForResultAsUser(intent, requestCode, null, user); + } + + /** + * @hide Implement to provide correct calling token. + */ + public void startActivityForResultAsUser(Intent intent, int requestCode, + @Nullable Bundle options, UserHandle user) { + startActivityForResultAsUser(intent, mEmbeddedID, requestCode, options, user); + } + + /** + * @hide Implement to provide correct calling token. + */ + public void startActivityForResultAsUser(Intent intent, String resultWho, int requestCode, + @Nullable Bundle options, 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); + } + + /** + * @hide Implement to provide correct calling token. + */ + public void startActivityAsUser(Intent intent, Bundle options, 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 permissionToken Token received from the system that permits this call to be made. + * @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, + IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { + 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, -1, options, permissionToken, ignoreTargetSecurity, userId); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, mEmbeddedID, -1, 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 #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, + 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); + } + } + + private void startIntentSenderForResultInner(IntentSender intent, String who, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, + Bundle options) + throws IntentSender.SendIntentException { + try { + options = transferSpringboardActivityOptions(options); + String resolvedType = null; + if (fillInIntent != null) { + fillInIntent.migrateExtraStreamToClipData(); + 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) { + 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. + */ + 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. + */ + public void startIntentSender(IntentSender intent, + @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + 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(); + intent.prepareToLeaveProcess(this); + result = ActivityTaskManager.getService() + .startActivity(mMainThread.getApplicationThread(), getBasePackageName(), + 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(); + 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 + */ + 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 + */ + 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 {@link android.support.v4.app.FragmentActivity#startActivityFromFragment( + * android.support.v4.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 {@link android.support.v4.app.FragmentActivity#startActivityFromFragment( + * android.support.v4.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); + } + + /** + * @hide + */ + public 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 + 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. + */ + 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. + */ + 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 #startIntentSenderFromChild}, but taking a Fragment; see + * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} + * for more information. + * + * @hide + */ + public void startIntentSenderFromChildFragment(Fragment child, IntentSender intent, + int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, + int extraFlags, @Nullable Bundle options) + throws IntentSender.SendIntentException { + startIntentSenderForResultInner(intent, child.mWho, requestCode, fillInIntent, + flagsMask, flagsValues, options); + } + + /** + * 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. + */ + public void overridePendingTransition(int enterAnim, int exitAnim) { + try { + ActivityTaskManager.getService().overridePendingTransition( + mToken, getPackageName(), enterAnim, exitAnim); + } catch (RemoteException e) { + } + } + + /** + * 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; + } + } + + /** + * 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(); + try { + Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER); + 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() { + try { + return ActivityTaskManager.getService().getCallingPackage(mToken); + } catch (RemoteException e) { + return null; + } + } + + /** + * 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() { + try { + return ActivityTaskManager.getService().getCallingActivity(mToken); + } catch (RemoteException e) { + return null; + } + } + + /** + * 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 + 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); + try { + if (resultData != null) { + resultData.prepareToLeaveProcess(this); + } + if (ActivityTaskManager.getService() + .finishActivity(mToken, resultCode, resultData, finishTask)) { + mFinished = true; + } + } catch (RemoteException e) { + // Empty + } + } else { + mParent.finishFromChild(this); + } + + // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must + // be restored now. + if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) { + getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_RESTORE, + mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)); + } + } + + /** + * 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"); + } + try { + if (ActivityTaskManager.getService().finishActivityAffinity(mToken)) { + mFinished = true; + } + } catch (RemoteException e) { + // Empty + } + } + + /** + * 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 + */ + 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) { + try { + ActivityTaskManager.getService() + .finishSubActivity(mToken, mEmbeddedID, requestCode); + } catch (RemoteException e) { + // Empty + } + } 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. + */ + public void finishActivityFromChild(@NonNull Activity child, int requestCode) { + try { + ActivityTaskManager.getService() + .finishSubActivity(mToken, child.mEmbeddedID, requestCode); + } catch (RemoteException e) { + // Empty + } + } + + /** + * 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() { + try { + return ActivityTaskManager.getService().releaseActivityInstance(mToken); + } catch (RemoteException e) { + // Empty + } + return false; + } + + /** + * 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>You will receive this call immediately before onResume() when your + * activity is re-starting. + * + * <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().getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, + 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) { + try { + ActivityTaskManager.getService().setRequestedOrientation( + mToken, requestedOrientation); + } catch (RemoteException e) { + // Empty + } + } 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) { + try { + return ActivityTaskManager.getService() + .getRequestedOrientation(mToken); + } catch (RemoteException e) { + // Empty + } + } else { + return mParent.getRequestedOrientation(); + } + return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + } + + /** + * 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() { + try { + return ActivityTaskManager.getService().getTaskForActivity(mToken, false); + } catch (RemoteException e) { + return -1; + } + } + + /** + * 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. + */ + @Override + public boolean isTaskRoot() { + try { + return ActivityTaskManager.getService().getTaskForActivity(mToken, true) >= 0; + } catch (RemoteException e) { + return false; + } + } + + /** + * 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) { + try { + return ActivityTaskManager.getService().moveActivityTaskToBack(mToken, nonRoot); + } catch (RemoteException e) { + // Empty + } + return false; + } + + /** + * 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 autofillClientGetComponentName() { + return getComponentName(); + } + + /** @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); + } + + 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); + } + } + try { + ActivityTaskManager.getService().setTaskDescription(mToken, mTaskDescription); + } catch (RemoteException e) { + } + } + + /** + * 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(); + } + } + + /** @hide */ + @Override + public final void autofillClientRunOnUiThread(Runnable action) { + runOnUiThread(action); + } + + /** + * 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 "adb shell dumpsys activity <activity_component_name>". + * + * @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); + } + + void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd, + @NonNull PrintWriter writer, @Nullable String[] args) { + if (args != null && args.length > 0) { + // Handle special cases + switch (args[0]) { + case "--autofill": + dumpAutofillManager(prefix, writer); + return; + case "--contentcapture": + dumpContentCaptureManager(prefix, writer); + return; + } + } + writer.print(prefix); writer.print("Local Activity "); + writer.print(Integer.toHexString(System.identityHashCode(this))); + writer.println(" State:"); + String innerPrefix = prefix + " "; + 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("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, fd, writer, args); + } + + mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); + + dumpAutofillManager(prefix, writer); + dumpContentCaptureManager(prefix, writer); + + ResourcesManager.getInstance().dump(prefix, writer); + } + + void dumpAutofillManager(String prefix, PrintWriter writer) { + final AutofillManager afm = getAutofillManager(); + if (afm != null) { + afm.dump(prefix, writer); + writer.print(prefix); writer.print("Autofill Compat Mode: "); + writer.println(isAutofillCompatibilityEnabled()); + } else { + writer.print(prefix); writer.println("No AutofillManager"); + } + } + + void dumpContentCaptureManager(String prefix, PrintWriter writer) { + final ContentCaptureManager cm = getContentCaptureManager(); + if (cm != null) { + cm.dump(prefix, writer); + } else { + writer.print(prefix); writer.println("No ContentCaptureManager"); + } + } + + /** + * 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() { + try { + return ActivityTaskManager.getService().isImmersive(mToken); + } catch (RemoteException e) { + return false; + } + } + + /** + * 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; + } + try { + return ActivityTaskManager.getService().isTopOfTask(getActivityToken()); + } catch (RemoteException e) { + return false; + } + } + + /** + * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} 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. + * <p> + * This call has no effect on non-translucent activities or on activities with the + * {@link android.R.attr#windowIsFloating} attribute. + * + * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener, + * ActivityOptions) + * @see TranslucentConversionListener + * + * @hide + */ + @SystemApi + public void convertFromTranslucent() { + try { + mTranslucentCallback = null; + if (ActivityTaskManager.getService().convertFromTranslucent(mToken)) { + WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true); + } + } catch (RemoteException e) { + // pass + } + } + + /** + * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} back from + * opaque to translucent following a call to {@link #convertFromTranslucent()}. + * <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. + * <p> + * This call has no effect on non-translucent activities or on activities with the + * {@link android.R.attr#windowIsFloating} attribute. + * + * @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) { + boolean drawComplete; + try { + mTranslucentCallback = callback; + mChangeCanvasToTranslucent = ActivityTaskManager.getService().convertToTranslucent( + mToken, options == null ? null : options.toBundle()); + WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false); + drawComplete = true; + } catch (RemoteException e) { + // Make callback return as though it timed out. + mChangeCanvasToTranslucent = false; + drawComplete = false; + } + if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) { + // Window is already translucent. + mTranslucentCallback.onTranslucentConversionComplete(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); + } + } + + /** + * Retrieve 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() { + try { + return ActivityOptions.fromBundle( + ActivityTaskManager.getService().getActivityOptions(mToken)); + } catch (RemoteException e) { + } + return null; + } + + /** + * 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) { + try { + ActivityTaskManager.getService().setImmersive(mToken, i); + } catch (RemoteException e) { + // pass + } + } + + /** + * 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 { + try { + if (ActivityTaskManager.getService().setVrMode(mToken, enabled, requestedComponent) + != 0) { + throw new PackageManager.NameNotFoundException( + requestedComponent.flattenToString()); + } + } catch (RemoteException e) { + // pass + } + } + + /** + * 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 ActivityTaskManager.getService().shouldUpRecreateTask(mToken, info.taskAffinity); + } catch (RemoteException e) { + return false; + } 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); + } + try { + upIntent.prepareToLeaveProcess(this); + return ActivityTaskManager.getService().navigateUpTo(mToken, upIntent, + resultCode, resultData); + } catch (RemoteException e) { + return false; + } + } 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. + */ + 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 + 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) { + attachBaseContext(context); + + mFragments.attachHost(null /*parent*/); + + mWindow = new PhoneWindow(this, window, activityConfigCallback); + mWindow.setWindowControllerCallback(this); + 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; + mIdent = ident; + mApplication = application; + mIntent = intent; + mReferrer = referrer; + mComponent = intent.getComponent(); + mActivityInfo = info; + 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); + + setAutofillOptions(application.getAutofillOptions()); + setContentCaptureOptions(application.getContentCaptureOptions()); + } + + private void enableAutofillCompatibilityIfNeeded() { + if (isAutofillCompatibilityEnabled()) { + final AutofillManager afm = getSystemService(AutofillManager.class); + if (afm != null) { + afm.enableCompatibilityMode(); + } + } + } + + /** @hide */ + @UnsupportedAppUsage + public final IBinder getActivityToken() { + return mParent != null ? mParent.getActivityToken() : mToken; + } + + /** @hide */ + public final IBinder getAssistToken() { + return mParent != null ? mParent.getAssistToken() : mAssistToken; + } + + /** @hide */ + @VisibleForTesting + public final ActivityThread getActivityThread() { + return mMainThread; + } + + final void performCreate(Bundle icicle) { + performCreate(icicle, null); + } + + @UnsupportedAppUsage + final void performCreate(Bundle icicle, PersistableBundle persistentState) { + dispatchActivityPreCreated(icicle); + mCanEnterPictureInPicture = true; + restoreHasCurrentPermissionRequest(icicle); + if (persistentState != null) { + onCreate(icicle, persistentState); + } else { + onCreate(icicle); + } + writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate"); + mActivityTransitionState.readState(icicle); + + mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, false); + mFragments.dispatchActivityCreated(); + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); + dispatchActivityPostCreated(icicle); + } + + final void performNewIntent(@NonNull Intent intent) { + mCanEnterPictureInPicture = true; + onNewIntent(intent); + } + + final void performStart(String reason) { + dispatchActivityPreStarted(); + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); + mFragments.noteStateNotSaved(); + mCalled = false; + mFragments.execPendingActions(); + mInstrumentation.callActivityOnStart(this); + writeEventLog(LOG_AM_ON_START_CALLED, reason); + + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onStart()"); + } + mFragments.dispatchStart(); + mFragments.reportLoaderStart(); + + boolean isAppDebuggable = + (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + + // This property is set for all non-user builds except final release + boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1; + + if (isAppDebuggable || isDlwarningEnabled) { + 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(); + } + + /** + * 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, String reason) { + 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; + mInstrumentation.callActivityOnRestart(this); + writeEventLog(LOG_AM_ON_RESTART_CALLED, reason); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onRestart()"); + } + if (start) { + performStart(reason); + } + } + } + + final void performResume(boolean followedByPause, String reason) { + dispatchActivityPreResumed(); + performRestart(true /* start */, reason); + + mFragments.execPendingActions(); + + mLastNonConfigurationInstances = null; + + if (mAutoFillResetNeeded) { + // When Activity is destroyed in paused state, and relaunch activity, there will be + // extra onResume and onPause event, ignore the first onResume and onPause. + // see ActivityThread.handleRelaunchActivity() + mAutoFillIgnoreFirstResumePause = followedByPause; + if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) { + Slog.v(TAG, "autofill will ignore first pause when relaunching " + this); + } + } + + mCalled = false; + // mResumed is set by the instrumentation + mInstrumentation.callActivityOnResume(this); + writeEventLog(LOG_AM_ON_RESUME_CALLED, reason); + 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(); + } + + final void performPause() { + dispatchActivityPrePaused(); + mDoReportFullyDrawn = false; + mFragments.dispatchPause(); + mCalled = false; + onPause(); + writeEventLog(LOG_AM_ON_PAUSE_CALLED, "performPause"); + 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(); + } + + final void performUserLeaving() { + onUserInteraction(); + onUserLeaveHint(); + } + + final void performStop(boolean preserveWindow, String reason) { + 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; + mInstrumentation.callActivityOnStop(this); + writeEventLog(LOG_AM_ON_STOP_CALLED, reason); + 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; + } + + final void performDestroy() { + dispatchActivityPreDestroyed(); + mDestroyed = true; + mWindow.destroy(); + mFragments.dispatchDestroy(); + onDestroy(); + writeEventLog(LOG_AM_ON_DESTROY_CALLED, "performDestroy"); + mFragments.doLoaderDestroy(); + if (mVoiceInteractor != null) { + mVoiceInteractor.detachActivity(); + } + dispatchActivityPostDestroyed(); + } + + 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(); + } + 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); + } + 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 + 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(AUTO_FILL_AUTH_WHO_PREFIX)) { + Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null; + getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus()); + } else { + Fragment frag = mFragments.findFragmentByWho(who); + if (frag != null) { + frag.onActivityResult(requestCode, resultCode, data); + } + } + writeEventLog(LOG_AM_ON_ACTIVITY_RESULT_CALLED, 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 + * whitelisted 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() { + try { + ActivityTaskManager.getService().startLockTaskModeByToken(mToken); + } catch (RemoteException e) { + } + } + + /** + * 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 whitelist authorization is revoked + * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}. + * + * @see #startLockTask() + * @see android.R.attr#lockTaskMode + * @see ActivityManager#getLockTaskModeState() + */ + public void stopLockTask() { + try { + ActivityTaskManager.getService().stopLockTaskModeByToken(mToken); + } catch (RemoteException e) { + } + } + + /** + * 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() { + try { + ActivityTaskManager.getService().showLockTaskEscapeMessage(mToken); + } catch (RemoteException e) { + } + } + + /** + * 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 */ + @Override + public final void autofillClientAuthenticate(int authenticationId, IntentSender intent, + Intent fillInIntent) { + try { + startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX, + authenticationId, fillInIntent, 0, 0, null); + } catch (IntentSender.SendIntentException e) { + Log.e(TAG, "authenticate() failed for intent:" + intent, e); + } + } + + /** @hide */ + @Override + public final void autofillClientResetableStateAvailable() { + mAutoFillResetNeeded = true; + } + + /** @hide */ + @Override + public final boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width, + int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) { + final boolean wasShowing; + + if (mAutofillPopupWindow == null) { + wasShowing = false; + mAutofillPopupWindow = new AutofillPopupWindow(presenter); + } else { + wasShowing = mAutofillPopupWindow.isShowing(); + } + mAutofillPopupWindow.update(anchor, 0, 0, width, height, anchorBounds); + + return !wasShowing && mAutofillPopupWindow.isShowing(); + } + + /** @hide */ + @Override + public final void autofillClientDispatchUnhandledKey(@NonNull View anchor, + @NonNull KeyEvent keyEvent) { + ViewRootImpl rootImpl = anchor.getViewRootImpl(); + if (rootImpl != null) { + // dont care if anchorView is current focus, for example a custom view may only receive + // touchEvent, not focusable but can still trigger autofill window. The Key handling + // might be inside parent of the custom view. + rootImpl.dispatchKeyFromAutofill(keyEvent); + } + } + + /** @hide */ + @Override + public final boolean autofillClientRequestHideFillUi() { + if (mAutofillPopupWindow == null) { + return false; + } + mAutofillPopupWindow.dismiss(); + mAutofillPopupWindow = null; + return true; + } + + /** @hide */ + @Override + public final boolean autofillClientIsFillUiShowing() { + return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing(); + } + + /** @hide */ + @Override + @NonNull + public final View[] autofillClientFindViewsByAutofillIdTraversal( + @NonNull AutofillId[] autofillId) { + final View[] views = new View[autofillId.length]; + final ArrayList<ViewRootImpl> roots = + WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); + + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + + if (rootView != null) { + final int viewCount = autofillId.length; + for (int viewNum = 0; viewNum < viewCount; viewNum++) { + if (views[viewNum] == null) { + views[viewNum] = rootView.findViewByAutofillIdTraversal( + autofillId[viewNum].getViewId()); + } + } + } + } + + return views; + } + + /** @hide */ + @Override + @Nullable + public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) { + final ArrayList<ViewRootImpl> roots = + WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + + if (rootView != null) { + final View view = rootView.findViewByAutofillIdTraversal(autofillId.getViewId()); + if (view != null) { + return view; + } + } + } + + return null; + } + + /** @hide */ + @Override + public final @NonNull boolean[] autofillClientGetViewVisibility( + @NonNull AutofillId[] autofillIds) { + final int autofillIdCount = autofillIds.length; + final boolean[] visible = new boolean[autofillIdCount]; + for (int i = 0; i < autofillIdCount; i++) { + final AutofillId autofillId = autofillIds[i]; + final View view = autofillClientFindViewByAutofillIdTraversal(autofillId); + if (view != null) { + if (!autofillId.isVirtualInt()) { + visible[i] = view.isVisibleToUser(); + } else { + visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId()); + } + } + } + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "autofillClientGetViewVisibility(): " + Arrays.toString(visible)); + } + return visible; + } + + /** @hide */ + public final @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId, + int windowId) { + final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance() + .getRootViews(getActivityToken()); + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + if (rootView != null && rootView.getAccessibilityWindowId() == windowId) { + final View view = rootView.findViewByAccessibilityIdTraversal(viewId); + if (view != null) { + return view; + } + } + } + return null; + } + + /** @hide */ + @Override + public final @Nullable IBinder autofillClientGetActivityToken() { + return getActivityToken(); + } + + /** @hide */ + @Override + public final boolean autofillClientIsVisibleForAutofill() { + return !mStopped; + } + + /** @hide */ + @Override + public final boolean autofillClientIsCompatibilityModeEnabled() { + return isAutofillCompatibilityEnabled(); + } + + /** @hide */ + @Override + public final boolean isDisablingEnterExitEventForAutofill() { + return mAutoFillIgnoreFirstResumePause || !mResumed; + } + + /** + * If set to true, this indicates to the system that it should never take a + * screenshot of the activity to be used as a representation while it is not in a started state. + * <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 disable {@code true} to disable preview screenshots; {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public void setDisablePreviewScreenshots(boolean disable) { + try { + ActivityTaskManager.getService().setDisablePreviewScreenshots(mToken, disable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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) { + try { + ActivityTaskManager.getService().setShowWhenLocked(mToken, showWhenLocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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) { + try { + ActivityTaskManager.getService().setInheritShowWhenLocked( + mToken, inheritShowWhenLocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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 + */ + public void setTurnScreenOn(boolean turnScreenOn) { + try { + ActivityTaskManager.getService().setTurnScreenOn(mToken, turnScreenOn); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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) + @UnsupportedAppUsage + public void registerRemoteAnimations(RemoteAnimationDefinition definition) { + try { + ActivityTaskManager.getService().registerRemoteAnimations(mToken, definition); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Log a lifecycle event for current user id and component class. */ + private void writeEventLog(int event, String reason) { + EventLog.writeEvent(event, UserHandle.myUserId(), getComponentName().getClassName(), + reason); + } + + 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.startIntentSenderFromChildFragment(fragment, intent, requestCode, + fillInIntent, flagsMask, flagsValues, extraFlags, 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); + } + } +}
diff --git a/android/app/ActivityGroup.java b/android/app/ActivityGroup.java new file mode 100644 index 0000000..d4aa01b --- /dev/null +++ b/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.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/app/ActivityManager.java b/android/app/ActivityManager.java new file mode 100644 index 0000000..17368b7 --- /dev/null +++ b/android/app/ActivityManager.java
@@ -0,0 +1,4326 @@ +/* + * 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.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; + +import android.Manifest; +import android.annotation.DrawableRes; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +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.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; +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.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.WorkSource; +import android.util.ArrayMap; +import android.util.DisplayMetrics; +import android.util.Singleton; +import android.util.Size; + +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.server.LocalServices; + +import org.xmlpull.v1.XmlSerializer; + +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.List; +import java.util.Locale; + +/** + * <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; + /** + * Mount full external storage for the newly started instrumentation. + * @hide + */ + public static final int INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL = 1 << 1; + + static final class UidObserver extends IUidObserver.Stub { + final OnUidImportanceListener mListener; + final Context mContext; + + UidObserver(OnUidImportanceListener listener, Context clientContext) { + mListener = listener; + mContext = clientContext; + } + + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq) { + mListener.onUidImportance(uid, RunningAppProcessInfo.procStateToImportanceForClient( + procState, mContext)); + } + + @Override + public void onUidGone(int uid, boolean disabled) { + mListener.onUidImportance(uid, RunningAppProcessInfo.IMPORTANCE_GONE); + } + + @Override + public void onUidActive(int uid) { + } + + @Override + public void onUidIdle(int uid, boolean disabled) { + } + + @Override public void onUidCachedChanged(int uid, boolean cached) { + } + } + + final ArrayMap<OnUidImportanceListener, UidObserver> mImportanceListeners = new ArrayMap<>(); + + /** + * Defines acceptable types of bugreports. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "BUGREPORT_OPTION_" }, value = { + BUGREPORT_OPTION_FULL, + BUGREPORT_OPTION_INTERACTIVE, + BUGREPORT_OPTION_REMOTE, + BUGREPORT_OPTION_WEAR, + BUGREPORT_OPTION_TELEPHONY, + BUGREPORT_OPTION_WIFI + }) + public @interface BugreportMode {} + /** + * Takes a bugreport without user interference (and hence causing less + * interference to the system), but includes all sections. + * @hide + */ + public static final int BUGREPORT_OPTION_FULL = 0; + /** + * Allows user to monitor progress and enter additional data; might not include all + * sections. + * @hide + */ + public static final int BUGREPORT_OPTION_INTERACTIVE = 1; + /** + * Takes a bugreport requested remotely by administrator of the Device Owner app, + * not the device's user. + * @hide + */ + public static final int BUGREPORT_OPTION_REMOTE = 2; + /** + * Takes a bugreport on a wearable device. + * @hide + */ + public static final int BUGREPORT_OPTION_WEAR = 3; + + /** + * Takes a lightweight version of bugreport that only includes a few, urgent sections + * used to report telephony bugs. + * @hide + */ + public static final int BUGREPORT_OPTION_TELEPHONY = 4; + + /** + * Takes a lightweight bugreport that only includes a few sections related to Wifi. + * @hide + */ + public static final int BUGREPORT_OPTION_WIFI = 5; + + /** + * <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 wasn't really started, but + * a task was simply 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; + + /** + * 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 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; + + /** + * @hide + * 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 Not a real process state. */ + public static final int PROCESS_STATE_UNKNOWN = -1; + + /** @hide Process is a persistent system process. */ + public static final int PROCESS_STATE_PERSISTENT = 0; + + /** @hide Process is a persistent system process and is doing UI. */ + public static final int PROCESS_STATE_PERSISTENT_UI = 1; + + /** @hide Process is hosting the current top activities. Note that this covers + * all activities that are visible to the user. */ + @UnsupportedAppUsage + public static final int PROCESS_STATE_TOP = 2; + + /** @hide Process is hosting a foreground service with location type. */ + public static final int PROCESS_STATE_FOREGROUND_SERVICE_LOCATION = 3; + + /** @hide Process is bound to a TOP app. This is ranked below SERVICE_LOCATION so that + * it doesn't get the capability of location access while-in-use. */ + public static final int PROCESS_STATE_BOUND_TOP = 4; + + /** @hide Process is hosting a foreground service. */ + @UnsupportedAppUsage + public static final int PROCESS_STATE_FOREGROUND_SERVICE = 5; + + /** @hide Process is hosting a foreground service due to a system binding. */ + @UnsupportedAppUsage + public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 6; + + /** @hide Process is important to the user, and something they are aware of. */ + public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 7; + + /** @hide Process is important to the user, but not something they are aware of. */ + @UnsupportedAppUsage + public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 8; + + /** @hide Process is in the background transient so we will try to keep running. */ + public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 9; + + /** @hide Process is in the background running a backup/restore operation. */ + public static final int PROCESS_STATE_BACKUP = 10; + + /** @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 + public static final int PROCESS_STATE_SERVICE = 11; + + /** @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 + public static final int PROCESS_STATE_RECEIVER = 12; + + /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */ + public static final int PROCESS_STATE_TOP_SLEEPING = 13; + + /** @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 = 14; + + /** @hide Process is in the background but hosts the home activity. */ + @UnsupportedAppUsage + public static final int PROCESS_STATE_HOME = 15; + + /** @hide Process is in the background but hosts the last shown activity. */ + public static final int PROCESS_STATE_LAST_ACTIVITY = 16; + + /** @hide Process is being cached for later use and contains activities. */ + @UnsupportedAppUsage + public static final int PROCESS_STATE_CACHED_ACTIVITY = 17; + + /** @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 = 18; + + /** @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 = 19; + + /** @hide Process is being cached for later use and is empty. */ + public static final int PROCESS_STATE_CACHED_EMPTY = 20; + + /** @hide Process does not exist. */ + public static final int PROCESS_STATE_NONEXISTENT = 21; + + // 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_LOCATION: + 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 Is this a foreground service type? */ + public static boolean isForegroundService(int procState) { + return procState == PROCESS_STATE_FOREGROUND_SERVICE_LOCATION + || 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 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 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); + + /** @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 + */ + @UnsupportedAppUsage + 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(); + } + + /** @removed */ + @Deprecated + public static int getMaxNumPictureInPictureActions() { + return 3; + } + + /** + * 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 + "colorBackground"; + 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 String mLabel; + private Bitmap mIcon; + private int mIconRes; + private String mIconFilename; + private int mColorPrimary; + private int mColorBackground; + private int mStatusBarColor; + private int mNavigationBarColor; + private boolean mEnsureStatusBarContrastWhenTransparent; + private boolean mEnsureNavigationBarContrastWhenTransparent; + + /** + * 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 TaskDescription constructor with icon resource instead + */ + @Deprecated + public TaskDescription(String label, Bitmap icon, int colorPrimary) { + this(label, icon, 0, null, colorPrimary, 0, 0, 0, false, false); + 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 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. + */ + public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) { + this(label, null, iconRes, null, colorPrimary, 0, 0, 0, false, false); + 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 TaskDescription constructor with icon resource instead + */ + @Deprecated + public TaskDescription(String label, Bitmap icon) { + this(label, icon, 0, null, 0, 0, 0, 0, false, false); + } + + /** + * 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. + */ + public TaskDescription(String label, @DrawableRes int iconRes) { + this(label, null, iconRes, null, 0, 0, 0, 0, false, false); + } + + /** + * Creates the TaskDescription to the specified values. + * + * @param label A label and description of the current state of this activity. + */ + public TaskDescription(String label) { + this(label, null, 0, null, 0, 0, 0, 0, false, false); + } + + /** + * Creates an empty TaskDescription. + */ + public TaskDescription() { + this(null, null, 0, null, 0, 0, 0, 0, false, false); + } + + /** @hide */ + public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename, + int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor, + boolean ensureStatusBarContrastWhenTransparent, + boolean ensureNavigationBarContrastWhenTransparent) { + mLabel = label; + mIcon = bitmap; + mIconRes = iconRes; + mIconFilename = iconFilename; + mColorPrimary = colorPrimary; + mColorBackground = colorBackground; + mStatusBarColor = statusBarColor; + mNavigationBarColor = navigationBarColor; + mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; + mEnsureNavigationBarContrastWhenTransparent = + ensureNavigationBarContrastWhenTransparent; + } + + /** + * 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; + mIconRes = other.mIconRes; + mIconFilename = other.mIconFilename; + mColorPrimary = other.mColorPrimary; + mColorBackground = other.mColorBackground; + mStatusBarColor = other.mStatusBarColor; + mNavigationBarColor = other.mNavigationBarColor; + mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; + mEnsureNavigationBarContrastWhenTransparent = + other.mEnsureNavigationBarContrastWhenTransparent; + } + + /** + * Copies this the values from another TaskDescription, but preserves the hidden fields + * if they weren't set on {@code other} + * @hide + */ + public void copyFromPreserveHiddenFields(TaskDescription other) { + mLabel = other.mLabel; + mIcon = other.mIcon; + mIconRes = other.mIconRes; + 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; + } + + 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; + } + + /** + * @hide + */ + public void setStatusBarColor(int statusBarColor) { + mStatusBarColor = statusBarColor; + } + + /** + * @hide + */ + public void setNavigationBarColor(int navigationBarColor) { + mNavigationBarColor = navigationBarColor; + } + + /** + * Sets the icon for this task description. + * @hide + */ + @UnsupportedAppUsage + public void setIcon(Bitmap icon) { + mIcon = icon; + } + + /** + * Sets the icon resource for this task description. + * @hide + */ + public void setIcon(int iconRes) { + mIconRes = iconRes; + } + + /** + * Moves the icon bitmap reference from an actual Bitmap to a file containing the + * bitmap. + * @hide + */ + public void setIconFilename(String iconFilename) { + mIconFilename = iconFilename; + mIcon = null; + } + + /** + * @return The label and description of the current state of this task. + */ + public String getLabel() { + return mLabel; + } + + /** + * @return The icon that represents the current state of this task. + */ + public Bitmap getIcon() { + if (mIcon != null) { + return mIcon; + } + return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId()); + } + + /** @hide */ + @TestApi + public int getIconResource() { + return mIconRes; + } + + /** @hide */ + @TestApi + public String getIconFilename() { + return mIconFilename; + } + + /** @hide */ + @UnsupportedAppUsage + public Bitmap getInMemoryIcon() { + return mIcon; + } + + /** @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. + */ + public int getPrimaryColor() { + return mColorPrimary; + } + + /** + * @return The background color. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public int getBackgroundColor() { + return mColorBackground; + } + + /** + * @hide + */ + public int getStatusBarColor() { + return mStatusBarColor; + } + + /** + * @hide + */ + 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 void saveToXml(XmlSerializer out) throws IOException { + if (mLabel != null) { + out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, mLabel); + } + if (mColorPrimary != 0) { + out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR_PRIMARY, + Integer.toHexString(mColorPrimary)); + } + if (mColorBackground != 0) { + out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND, + Integer.toHexString(mColorBackground)); + } + if (mIconFilename != null) { + out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename); + } + if (mIconRes != 0) { + out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes)); + } + } + + /** @hide */ + public void restoreFromXml(String attrName, String attrValue) { + if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) { + setLabel(attrValue); + } else if (ATTR_TASKDESCRIPTIONCOLOR_PRIMARY.equals(attrName)) { + setPrimaryColor((int) Long.parseLong(attrValue, 16)); + } else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) { + setBackgroundColor((int) Long.parseLong(attrValue, 16)); + } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) { + setIconFilename(attrValue); + } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) { + setIcon(Integer.parseInt(attrValue, 10)); + } + } + + @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); + } + if (mIcon == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + mIcon.writeToParcel(dest, 0); + } + dest.writeInt(mIconRes); + dest.writeInt(mColorPrimary); + dest.writeInt(mColorBackground); + dest.writeInt(mStatusBarColor); + dest.writeInt(mNavigationBarColor); + dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent); + dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent); + if (mIconFilename == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeString(mIconFilename); + } + } + + public void readFromParcel(Parcel source) { + mLabel = source.readInt() > 0 ? source.readString() : null; + mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null; + mIconRes = source.readInt(); + mColorPrimary = source.readInt(); + mColorBackground = source.readInt(); + mStatusBarColor = source.readInt(); + mNavigationBarColor = source.readInt(); + mEnsureStatusBarContrastWhenTransparent = source.readBoolean(); + mEnsureNavigationBarContrastWhenTransparent = source.readBoolean(); + mIconFilename = source.readInt() > 0 ? source.readString() : null; + } + + 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 + + " IconRes: " + mIconRes + " IconFilename: " + mIconFilename + + " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground + + " statusBarColor: " + mStatusBarColor + ( + mEnsureStatusBarContrastWhenTransparent ? " (contrast when transparent)" + : "") + " navigationBarColor: " + mNavigationBarColor + ( + mEnsureNavigationBarContrastWhenTransparent + ? " (contrast when transparent)" : ""); + } + } + + /** + * Information you can retrieve about tasks that the user has most recently + * started or visited. + */ + public static class RecentTaskInfo extends TaskInfo implements Parcelable { + /** + * 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; + + 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(); + super.readFromParcel(source); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeInt(persistentId); + 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) { + final String activityType = WindowConfiguration.activityTypeToString( + configuration.windowConfiguration.getActivityType()); + final String windowingMode = WindowConfiguration.activityTypeToString( + configuration.windowConfiguration.getActivityType()); + + pw.println(); pw.print(" "); + pw.print(" id=" + persistentId); + pw.print(" stackId=" + stackId); + pw.print(" userId=" + userId); + pw.print(" hasTask=" + (id != -1)); + pw.print(" lastActiveTime=" + lastActiveTime); + pw.println(); pw.print(" "); + pw.print(" baseIntent=" + baseIntent); + pw.println(); pw.print(" "); + pw.print(" isExcluded=" + + ((baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0)); + pw.print(" activityType=" + activityType); + pw.print(" windowingMode=" + windowingMode); + pw.print(" supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow); + if (taskDescription != null) { + pw.println(); pw.print(" "); + final ActivityManager.TaskDescription td = taskDescription; + pw.print(" taskDescription {"); + pw.print(" colorBackground=#" + Integer.toHexString(td.getBackgroundColor())); + pw.print(" colorPrimary=#" + Integer.toHexString(td.getPrimaryColor())); + pw.print(" iconRes=" + (td.getIconResource() != 0)); + pw.print(" iconBitmap=" + (td.getIconFilename() != null + || td.getInMemoryIcon() != null)); + 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 { + try { + if (maxNum < 0) { + throw new IllegalArgumentException("The requested number of tasks should be >= 0"); + } + return getTaskService().getRecentTasks(maxNum, flags, mContext.getUserId()).getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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.getPackageName()); + } 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 { + try { + return getTaskService().getTasks(maxNum); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Represents a task snapshot. + * @hide + */ + public static class TaskSnapshot implements Parcelable { + + // Top activity in task when snapshot was taken + private final ComponentName mTopActivityComponent; + private final GraphicBuffer mSnapshot; + private final int mOrientation; + private final Rect mContentInsets; + // Whether this snapshot is a down-sampled version of the full resolution, used mainly for + // low-ram devices + private final boolean mReducedResolution; + // Whether or not the snapshot is a real snapshot or an app-theme generated snapshot due to + // the task having a secure window or having previews disabled + private final boolean mIsRealSnapshot; + private final int mWindowingMode; + private final float mScale; + private final int mSystemUiVisibility; + private final boolean mIsTranslucent; + // Must be one of the named color spaces, otherwise, always use SRGB color space. + private final ColorSpace mColorSpace; + + public TaskSnapshot(@NonNull ComponentName topActivityComponent, GraphicBuffer snapshot, + @NonNull ColorSpace colorSpace, int orientation, Rect contentInsets, + boolean reducedResolution, float scale, boolean isRealSnapshot, int windowingMode, + int systemUiVisibility, boolean isTranslucent) { + mTopActivityComponent = topActivityComponent; + mSnapshot = snapshot; + mColorSpace = colorSpace.getId() < 0 + ? ColorSpace.get(ColorSpace.Named.SRGB) : colorSpace; + mOrientation = orientation; + mContentInsets = new Rect(contentInsets); + mReducedResolution = reducedResolution; + mScale = scale; + mIsRealSnapshot = isRealSnapshot; + mWindowingMode = windowingMode; + mSystemUiVisibility = systemUiVisibility; + mIsTranslucent = isTranslucent; + } + + private TaskSnapshot(Parcel source) { + mTopActivityComponent = ComponentName.readFromParcel(source); + mSnapshot = source.readParcelable(null /* classLoader */); + int colorSpaceId = source.readInt(); + mColorSpace = colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length + ? ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]) + : ColorSpace.get(ColorSpace.Named.SRGB); + mOrientation = source.readInt(); + mContentInsets = source.readParcelable(null /* classLoader */); + mReducedResolution = source.readBoolean(); + mScale = source.readFloat(); + mIsRealSnapshot = source.readBoolean(); + mWindowingMode = source.readInt(); + mSystemUiVisibility = source.readInt(); + mIsTranslucent = source.readBoolean(); + } + + /** + * @return The top activity component for the task at the point this snapshot was taken. + */ + public ComponentName getTopActivityComponent() { + return mTopActivityComponent; + } + + /** + * @return The graphic buffer representing the screenshot. + */ + @UnsupportedAppUsage + public GraphicBuffer getSnapshot() { + return mSnapshot; + } + + /** + * @return The color space of graphic buffer representing the screenshot. + */ + public ColorSpace getColorSpace() { + return mColorSpace; + } + + /** + * @return The screen orientation the screenshot was taken in. + */ + @UnsupportedAppUsage + public int getOrientation() { + return mOrientation; + } + + /** + * @return The system/content insets on the snapshot. These can be clipped off in order to + * remove any areas behind system bars in the snapshot. + */ + @UnsupportedAppUsage + public Rect getContentInsets() { + return mContentInsets; + } + + /** + * @return Whether this snapshot is a down-sampled version of the full resolution. + */ + @UnsupportedAppUsage + public boolean isReducedResolution() { + return mReducedResolution; + } + + /** + * @return Whether or not the snapshot is a real snapshot or an app-theme generated snapshot + * due to the task having a secure window or having previews disabled. + */ + @UnsupportedAppUsage + public boolean isRealSnapshot() { + return mIsRealSnapshot; + } + + /** + * @return Whether or not the snapshot is of a translucent app window (non-fullscreen or has + * a non-opaque pixel format). + */ + public boolean isTranslucent() { + return mIsTranslucent; + } + + /** + * @return The windowing mode of the task when this snapshot was taken. + */ + public int getWindowingMode() { + return mWindowingMode; + } + + /** + * @return The system ui visibility flags for the top most visible fullscreen window at the + * time that the snapshot was taken. + */ + public int getSystemUiVisibility() { + return mSystemUiVisibility; + } + + /** + * @return The scale this snapshot was taken in. + */ + @UnsupportedAppUsage + public float getScale() { + return mScale; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + ComponentName.writeToParcel(mTopActivityComponent, dest); + dest.writeParcelable(mSnapshot, 0); + dest.writeInt(mColorSpace.getId()); + dest.writeInt(mOrientation); + dest.writeParcelable(mContentInsets, 0); + dest.writeBoolean(mReducedResolution); + dest.writeFloat(mScale); + dest.writeBoolean(mIsRealSnapshot); + dest.writeInt(mWindowingMode); + dest.writeInt(mSystemUiVisibility); + dest.writeBoolean(mIsTranslucent); + } + + @Override + public String toString() { + final int width = mSnapshot != null ? mSnapshot.getWidth() : 0; + final int height = mSnapshot != null ? mSnapshot.getHeight() : 0; + return "TaskSnapshot{" + + " mTopActivityComponent=" + mTopActivityComponent.flattenToShortString() + + " mSnapshot=" + mSnapshot + " (" + width + "x" + height + ")" + + " mColorSpace=" + mColorSpace.toString() + + " mOrientation=" + mOrientation + + " mContentInsets=" + mContentInsets.toShortString() + + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale + + " mIsRealSnapshot=" + mIsRealSnapshot + " mWindowingMode=" + mWindowingMode + + " mSystemUiVisibility=" + mSystemUiVisibility + + " mIsTranslucent=" + mIsTranslucent; + } + + public static final @android.annotation.NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() { + public TaskSnapshot createFromParcel(Parcel source) { + return new TaskSnapshot(source); + } + public TaskSnapshot[] newArray(int size) { + return new TaskSnapshot[size]; + } + }; + } + + /** @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.getPackageName(); + 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 + * @see android.view.Display.TYPE_VIRTUAL + * + * @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 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 + public long hiddenAppThreshold; + /** @hide */ + @UnsupportedAppUsage + public long secondaryServerThreshold; + /** @hide */ + @UnsupportedAppUsage + public long visibleAppThreshold; + /** @hide */ + @UnsupportedAppUsage + public long foregroundAppThreshold; + + public MemoryInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + 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) { + 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(); + } + } + + /** + * Information you can retrieve about an ActivityStack in the system. + * @hide + */ + public static class StackInfo implements Parcelable { + @UnsupportedAppUsage + public int stackId; + @UnsupportedAppUsage + public Rect bounds = new Rect(); + @UnsupportedAppUsage + public int[] taskIds; + @UnsupportedAppUsage + public String[] taskNames; + @UnsupportedAppUsage + public Rect[] taskBounds; + @UnsupportedAppUsage + public int[] taskUserIds; + @UnsupportedAppUsage + public ComponentName topActivity; + @UnsupportedAppUsage + public int displayId; + @UnsupportedAppUsage + public int userId; + @UnsupportedAppUsage + public boolean visible; + // Index of the stack in the display's stack list, can be used for comparison of stack order + @UnsupportedAppUsage + public int position; + /** + * The full configuration the stack is currently running in. + * @hide + */ + final public Configuration configuration = new Configuration(); + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(stackId); + dest.writeInt(bounds.left); + dest.writeInt(bounds.top); + dest.writeInt(bounds.right); + dest.writeInt(bounds.bottom); + dest.writeIntArray(taskIds); + dest.writeStringArray(taskNames); + final int boundsCount = taskBounds == null ? 0 : taskBounds.length; + dest.writeInt(boundsCount); + for (int i = 0; i < boundsCount; i++) { + dest.writeInt(taskBounds[i].left); + dest.writeInt(taskBounds[i].top); + dest.writeInt(taskBounds[i].right); + dest.writeInt(taskBounds[i].bottom); + } + dest.writeIntArray(taskUserIds); + dest.writeInt(displayId); + dest.writeInt(userId); + dest.writeInt(visible ? 1 : 0); + dest.writeInt(position); + if (topActivity != null) { + dest.writeInt(1); + topActivity.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + configuration.writeToParcel(dest, flags); + } + + public void readFromParcel(Parcel source) { + stackId = source.readInt(); + bounds = new Rect( + source.readInt(), source.readInt(), source.readInt(), source.readInt()); + taskIds = source.createIntArray(); + taskNames = source.createStringArray(); + final int boundsCount = source.readInt(); + if (boundsCount > 0) { + taskBounds = new Rect[boundsCount]; + for (int i = 0; i < boundsCount; i++) { + taskBounds[i] = new Rect(); + taskBounds[i].set( + source.readInt(), source.readInt(), source.readInt(), source.readInt()); + } + } else { + taskBounds = null; + } + taskUserIds = source.createIntArray(); + displayId = source.readInt(); + userId = source.readInt(); + visible = source.readInt() > 0; + position = source.readInt(); + if (source.readInt() > 0) { + topActivity = ComponentName.readFromParcel(source); + } + configuration.readFromParcel(source); + } + + public static final @android.annotation.NonNull Creator<StackInfo> CREATOR = new Creator<StackInfo>() { + @Override + public StackInfo createFromParcel(Parcel source) { + return new StackInfo(source); + } + @Override + public StackInfo[] newArray(int size) { + return new StackInfo[size]; + } + }; + + public StackInfo() { + } + + private StackInfo(Parcel source) { + readFromParcel(source); + } + + @UnsupportedAppUsage + public String toString(String prefix) { + StringBuilder sb = new StringBuilder(256); + sb.append(prefix); sb.append("Stack id="); sb.append(stackId); + 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"); + prefix = prefix + " "; + for (int i = 0; i < taskIds.length; ++i) { + sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]); + sb.append(": "); sb.append(taskNames[i]); + if (taskBounds != null) { + sb.append(" bounds="); sb.append(taskBounds[i].toShortString()); + } + sb.append(" userId=").append(taskUserIds[i]); + sb.append(" visible=").append(visible); + if (topActivity != null) { + sb.append(" topActivity=").append(topActivity); + } + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(""); + } + } + + /** + * @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. + * + * @return Returns a list of 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[]; + + /** + * 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 + public static final int FLAG_PERSISTENT = 1<<1; + + /** + * Constant for {@link #flags}: this process is associated with a + * persistent system app. + * @hide + */ + @UnsupportedAppUsage + 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 + */ + @TestApi + public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170; + + /** + * Constant for {@link #importance}: This process is 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 + 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_LOCATION) { + 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.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(); + 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 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 @TestApi + @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 @TestApi + @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 @TestApi + 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 @TestApi + @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. + UidObserver observer = new UidObserver(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 @TestApi + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public void removeOnUidImportanceListener(OnUidImportanceListener listener) { + synchronized (this) { + UidObserver 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. + * + * @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 @TestApi + @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES) + public void forceStopPackage(String packageName) { + forceStopPackageAsUser(packageName, mContext.getUserId()); + } + + /** + * 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 */ + @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 (appId == Process.ROOT_UID || appId == Process.SYSTEM_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() { + UserInfo ui; + try { + ui = getService().getCurrentUser(); + return ui != null ? ui.id : 0; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @param userid the user's id. Zero indicates the default user. + * @hide + */ + @UnsupportedAppUsage + 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(android.Manifest.permission.MANAGE_USERS) + public boolean switchUser(@NonNull UserHandle user) { + if (user == null) { + throw new IllegalArgumentException("UserHandle cannot be null."); + } + return switchUser(user.getIdentifier()); + } + + /** + * Logs out current current foreground user by switching to the system user and stopping the + * user being switched from. + * @hide + */ + public static void logoutCurrentUser() { + int currentUser = ActivityManager.getCurrentUser(); + if (currentUser != UserHandle.USER_SYSTEM) { + try { + getService().switchUser(UserHandle.USER_SYSTEM); + getService().stopUser(currentUser, /* force= */ false, null); + } catch (RemoteException e) { + 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, userId); + } + + /** + * Convenience for sending a sticky broadcast. For internal use only. + * + * @hide + */ + public static void broadcastStickyIntent(Intent intent, int appOp, int userId) { + try { + getService().broadcastIntent( + null, intent, null, null, Activity.RESULT_OK, null, null, + null /*permission*/, appOp, null, 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 activty 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(); + } + } + + /** + * 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(); + } + } + } +}
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java new file mode 100644 index 0000000..8508c2c --- /dev/null +++ b/android/app/ActivityManagerInternal.java
@@ -0,0 +1,359 @@ +/* + * 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.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.IIntentReceiver; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ActivityPresentationInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.TransactionTooLargeException; +import android.view.RemoteAnimationAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Activity manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class ActivityManagerInternal { + + + // Access modes for handleIncomingUser. + public static final int ALLOW_NON_FULL = 0; + public static final int ALLOW_NON_FULL_IN_PROFILE = 1; + public static final int ALLOW_FULL_ONLY = 2; + + /** + * Verify that calling app has access to the given provider. + */ + public abstract String checkContentProviderAccess(String authority, int userId); + + // 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); + + /** + * Kill foreground apps from the specified user. + */ + public abstract void killForegroundAppsForUser(int userHandle); + + /** + * Sets how long a {@link PendingIntent} can be temporarily whitelist to by bypass restrictions + * such as Power Save mode. + */ + public abstract void setPendingIntentWhitelistDuration(IIntentSender target, + IBinder whitelistToken, long duration); + + /** + * Allows for a {@link PendingIntent} to be whitelisted to start activities from background. + */ + public abstract void setPendingIntentAllowBgActivityStarts( + IIntentSender target, IBinder whitelistToken, int flags); + + /** + * Voids {@link PendingIntent}'s privilege to be whitelisted to start activities from + * background. + */ + public abstract void clearPendingIntentAllowBgActivityStarts(IIntentSender target, + IBinder whitelistToken); + + /** + * Allow DeviceIdleController to tell us about what apps are whitelisted. + */ + public abstract void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids); + + /** + * Update information about which app IDs are on the temp whitelist. + */ + public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingAppId, + boolean adding); + + /** + * 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); + + /** + * @return {@code true} if system is ready, {@code false} otherwise. + */ + public abstract boolean isSystemReady(); + + /** + * 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); + + /** + * Sets if the given pid is currently running a remote animation, which is taken a signal for + * determining oom adjustment and scheduling behavior. + * + * @param pid The pid we are setting overlay UI for. + * @param runningRemoteAnimation True if the process is running a remote animation, false + * otherwise. + * @see RemoteAnimationAdapter + */ + public abstract void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation); + + /** + * 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); + + /** + * @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, int userId, + boolean allowAll, int allowMode, String name, String callerPackage); + + /** Checks if the calling binder pid as the permission. */ + public abstract void enforceCallingPermission(String permission, String func); + + /** Returns the current user id. */ + public abstract int getCurrentUserId(); + + /** Returns true if the user is running. */ + public abstract boolean isUserRunning(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); + + public abstract void updateOomAdj(); + 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, int userId, boolean resumed); + + /** + * Update UsageStats of the activity. + * @param activity + * @param userId + * @param event + * @param appToken ActivityRecord's appToken. + * @param taskRoot TaskRecord's root + */ + public abstract void updateActivityUsageStats( + ComponentName activity, int userId, int event, IBinder appToken, + ComponentName taskRoot); + public abstract void updateForegroundTimeIfOnBattery( + String packageName, int uid, long cpuTimeDiff); + public abstract void sendForegroundProfileChanged(int userId); + + /** + * Returns whether the given user requires credential entry at this time. This is used to + * intercept activity launches for work apps when the Work Challenge is present. + */ + public abstract boolean shouldConfirmCredentials(int userId); + + public abstract int[] getCurrentProfileIds(); + public abstract UserInfo getCurrentUser(); + public abstract void ensureNotSpecialUser(int userId); + public abstract boolean isCurrentProfile(int userId); + public abstract boolean hasStartedUserState(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(); + + public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid, + long duration, String tag); + public abstract int broadcastIntentInPackage(String packageName, int uid, int realCallingUid, + int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo, + int resultCode, String resultData, Bundle resultExtras, String requiredPermission, + Bundle bOptions, boolean serialized, boolean sticky, int userId, + boolean allowBackgroundActivityStarts); + public abstract ComponentName startServiceInPackage(int uid, Intent service, + String resolvedType, boolean fgRequired, String callingPackage, int userId, + boolean allowBackgroundActivityStarts) throws TransactionTooLargeException; + + public abstract void disconnectActivityFromServices(Object connectionHolder, Object conns); + public abstract void cleanUpServices(int userId, ComponentName component, Intent baseIntent); + public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, 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(); + public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing); + + /** Input dispatch timeout to a window, start the ANR process. */ + public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason); + public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName, + ApplicationInfo aInfo, String parentShortComponentName, Object parentProc, + boolean aboveSystem, String reason); + + /** + * 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); + + /** + * 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, 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 uid is currently marked 'bad' */ + public abstract boolean isAppBad(ApplicationInfo info); + + /** Remove pending backup for the given userId. */ + public abstract void clearPendingBackup(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); + + /** + * 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); +}
diff --git a/android/app/ActivityManagerNative.java b/android/app/ActivityManagerNative.java new file mode 100644 index 0000000..37509e1 --- /dev/null +++ b/android/app/ActivityManagerNative.java
@@ -0,0 +1,99 @@ +/* + * 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.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 { + /** + * 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/app/ActivityOptions.java b/android/app/ActivityOptions.java new file mode 100644 index 0000000..926044b --- /dev/null +++ b/android/app/ActivityOptions.java
@@ -0,0 +1,1716 @@ +/* + * 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_REMOTE_APP_TRANSITION_ANIMATIONS; +import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.INVALID_DISPLAY; + +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.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.GraphicBuffer; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.UserHandle; +import android.transition.Transition; +import android.transition.TransitionListenerAdapter; +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 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 { + 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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"; + + /** + * 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 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 #setPendingIntentLaunchFlags(int)} + * @hide + */ + private static final String KEY_PENDING_INTENT_LAUNCH_FLAGS = + "android.activity.pendingIntentLaunchFlags"; + + /** + * 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"; + + /** + * Where the split-screen-primary stack should be positioned. + * @hide + */ + private static final String KEY_SPLIT_SCREEN_CREATE_MODE = + "android:activity.splitScreenCreateMode"; + + /** + * 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"; + + /** + * 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"; + + 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"; + + /** @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; + + private String mPackageName; + private Rect mLaunchBounds; + private int mAnimationType = ANIM_NONE; + private int mCustomEnterResId; + private int mCustomExitResId; + private int mCustomInPlaceResId; + 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; + @WindowConfiguration.WindowingMode + private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED; + @WindowConfiguration.ActivityType + private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED; + private int mLaunchTaskId = -1; + private int mPendingIntentLaunchFlags; + private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; + private boolean mLockTaskMode = false; + private boolean mDisallowEnterPictureInPictureWhileLaunching; + private boolean mTaskOverlay; + private boolean mTaskOverlayCanResume; + private boolean mAvoidMoveToFront; + private boolean mFreezeRecentTasksReordering; + private AppTransitionAnimationSpec mAnimSpecs[]; + private int mRotationAnimationHint = -1; + private Bundle mAppVerificationBundle; + private IAppTransitionAnimationSpecsFuture mSpecsFuture; + private RemoteAnimationAdapter mRemoteAnimationAdapter; + + /** + * 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, 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, Handler handler, OnAnimationStartedListener listener) { + ActivityOptions opts = new ActivityOptions(); + opts.mPackageName = context.getPackageName(); + opts.mAnimationType = ANIM_CUSTOM; + opts.mCustomEnterResId = enterResId; + opts.mCustomExitResId = exitResId; + opts.setOnAnimationStartedListener(handler, listener); + 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 { + handler.post(new Runnable() { + @Override public void run() { + listener.onAnimationStarted(); + } + }); + } + }; + } + } + + /** + * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation} + * to find out when the given animation has started running. + * @hide + */ + public interface OnAnimationStartedListener { + void onAnimationStarted(); + } + + private void setOnAnimationFinishedListener(final Handler handler, + final OnAnimationFinishedListener listener) { + if (listener != null) { + mAnimationFinishedListener = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAnimationFinished(); + } + }); + } + }; + } + } + + /** + * Callback for use with {@link ActivityOptions#makeThumbnailAspectScaleDownAnimation} + * to find out when the given animation has drawn its last frame. + * @hide + */ + public interface OnAnimationFinishedListener { + void onAnimationFinished(); + } + + /** + * 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(); + makeSceneTransitionAnimation(activity, activity.getWindow(), opts, + activity.mExitTransitionListener, sharedElements); + 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 ActivityOptions startSharedElementAnimation(Window window, + Pair<View, String>... sharedElements) { + ActivityOptions opts = new ActivityOptions(); + final View decorView = window.getDecorView(); + if (decorView == null) { + return opts; + } + final ExitTransitionCoordinator exit = + makeSceneTransitionAnimation(null, window, opts, null, sharedElements); + if (exit != null) { + HideWindowListener listener = new HideWindowListener(window, exit); + exit.setHideSharedElementsCallback(listener); + exit.startExit(); + } + return opts; + } + + /** + * This method should be called when the {@link #startSharedElementAnimation(Window, 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(Activity activity, Window window, + ActivityOptions opts, SharedElementCallback callback, + 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(activity, window, + callback, names, names, views, false); + opts.mTransitionReceiver = exit; + opts.mSharedElementNames = names; + opts.mIsReturning = (activity == null); + if (activity == null) { + opts.mExitCoordinatorIndex = -1; + } else { + opts.mExitCoordinatorIndex = + activity.mActivityTransitionState.addExitTransitionCoordinator(exit); + } + return exit; + } + + /** @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; + 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; + } + + /** @hide */ + public boolean getLaunchTaskBehind() { + return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; + } + + private ActivityOptions() { + } + + /** @hide */ + public ActivityOptions(Bundle opts) { + // If the remote side sent us bad parcelables, they won't get the + // results they want, which is their loss. + opts.setDefusable(true); + + mPackageName = opts.getString(KEY_PACKAGE_NAME); + try { + mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT); + } catch (RuntimeException e) { + Slog.w(TAG, e); + } + mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS); + mAnimationType = opts.getInt(KEY_ANIM_TYPE); + switch (mAnimationType) { + case ANIM_CUSTOM: + mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0); + mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 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 GraphicBuffer from the parceled thumbnail + final GraphicBuffer buffer = opts.getParcelable(KEY_ANIM_THUMBNAIL); + 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); + mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false); + mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS); + mResultData = opts.getParcelable(KEY_RESULT_DATA); + mResultCode = opts.getInt(KEY_RESULT_CODE); + mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX); + break; + } + mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false); + mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY); + 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); + 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); + mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE, + SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); + mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean( + KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, 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)); + } + 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); + } + + /** + * 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; + } + + /** + * 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 GraphicBuffer on the other end. + * + * @hide + */ + public GraphicBuffer getThumbnail() { + return mThumbnail != null ? mThumbnail.createGraphicBufferHandle() : 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 getOnAnimationStartListener() { + 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 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; + } + + /** + * 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; + } + + /** + * 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 activity should be launched. + * An app can launch activities on public displays or private displays that are owned by the app + * or where an 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 getLaunchWindowingMode() { + return mLaunchWindowingMode; + } + + /** + * Sets the windowing mode the activity should launch into. If the input windowing mode is + * {@link android.app.WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} and the device + * isn't currently in split-screen windowing mode, then the activity will be launched in + * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN} windowing mode. For clarity + * on this you can use + * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY} + * + * @hide + */ + @TestApi + public void setLaunchWindowingMode(int windowingMode) { + mLaunchWindowingMode = windowingMode; + } + + /** @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 + */ + @TestApi + public void setLaunchTaskId(int taskId) { + mLaunchTaskId = taskId; + } + + /** + * @hide + */ + public int getLaunchTaskId() { + return mLaunchTaskId; + } + + /** + * 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() { + return mPendingIntentLaunchFlags; + } + + /** + * 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. If {@param canResume} is true, then + * 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 */ + public int getSplitScreenCreateMode() { + return mSplitScreenCreateMode; + } + + /** @hide */ + @UnsupportedAppUsage + public void setSplitScreenCreateMode(int splitScreenCreateMode) { + mSplitScreenCreateMode = splitScreenCreateMode; + } + + /** @hide */ + public void setDisallowEnterPictureInPictureWhileLaunching(boolean disallow) { + mDisallowEnterPictureInPictureWhileLaunching = disallow; + } + + /** @hide */ + public boolean disallowEnterPictureInPictureWhileLaunching() { + return mDisallowEnterPictureInPictureWhileLaunching; + } + + /** + * 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; + 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; + mAnimSpecs = otherOptions.mAnimSpecs; + mAnimationFinishedListener = otherOptions.mAnimationFinishedListener; + mSpecsFuture = otherOptions.mSpecsFuture; + mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter; + } + + /** + * 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. + */ + public Bundle toBundle() { + Bundle b = new Bundle(); + if (mPackageName != null) { + b.putString(KEY_PACKAGE_NAME, mPackageName); + } + if (mLaunchBounds != null) { + b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds); + } + 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.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 GraphicBuffer + if (mThumbnail != null) { + final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, false /* isMutable */); + if (hwBitmap != null) { + b.putParcelable(KEY_ANIM_THUMBNAIL, hwBitmap.createGraphicBufferHandle()); + } 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 (mLaunchDisplayId != INVALID_DISPLAY) { + b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId); + } + 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 (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 (mSplitScreenCreateMode != SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT) { + b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode); + } + if (mDisallowEnterPictureInPictureWhileLaunching) { + b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, + mDisallowEnterPictureInPictureWhileLaunching); + } + 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 (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); + } + 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; + } + + /** + * 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; + + } + + /** @hide */ + @Override + public String toString() { + return "ActivityOptions(" + hashCode() + "), mPackageName=" + mPackageName + + ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY=" + + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight; + } + + private static class HideWindowListener extends TransitionListenerAdapter + implements ExitTransitionCoordinator.HideSharedElementsCallback { + private final Window mWindow; + private final ExitTransitionCoordinator mExit; + private final boolean mWaitingForTransition; + private boolean mTransitionEnded; + private boolean mSharedElementHidden; + private ArrayList<View> mSharedElements; + + public HideWindowListener(Window window, ExitTransitionCoordinator exit) { + mWindow = window; + mExit = exit; + mSharedElements = new ArrayList<>(exit.mSharedElements); + Transition transition = mWindow.getExitTransition(); + if (transition != null) { + transition.addListener(this); + mWaitingForTransition = true; + } else { + mWaitingForTransition = false; + } + View decorView = mWindow.getDecorView(); + if (decorView != null) { + if (decorView.getTag(com.android.internal.R.id.cross_task_transition) != null) { + throw new IllegalStateException( + "Cannot start a transition while one is running"); + } + decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, exit); + } + } + + @Override + public void onTransitionEnd(Transition transition) { + mTransitionEnded = true; + hideWhenDone(); + transition.removeListener(this); + } + + @Override + public void hideSharedElements() { + mSharedElementHidden = true; + hideWhenDone(); + } + + private void hideWhenDone() { + if (mSharedElementHidden && (!mWaitingForTransition || mTransitionEnded)) { + mExit.resetViews(); + int numSharedElements = mSharedElements.size(); + for (int i = 0; i < numSharedElements; i++) { + View view = mSharedElements.get(i); + view.requestLayout(); + } + View decorView = mWindow.getDecorView(); + if (decorView != null) { + decorView.setTagInternal( + com.android.internal.R.id.cross_task_transition, null); + decorView.setVisibility(View.GONE); + } + } + } + } +}
diff --git a/android/app/ActivityTaskManager.java b/android/app/ActivityTaskManager.java new file mode 100644 index 0000000..4ef554d --- /dev/null +++ b/android/app/ActivityTaskManager.java
@@ -0,0 +1,448 @@ +/* + * 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 android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Singleton; + +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; + + /** + * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which + * specifies the position of the created docked stack at the top half of the screen if + * in portrait mode or at the left half of the screen if in landscape mode. + */ + public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; + + /** + * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which + * specifies the position of the created docked stack at the bottom half of the screen if + * in portrait mode or at the right half of the screen if in landscape mode. + */ + public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 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 are delegating the call to + * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call + * to succeed. Type is IBinder. + * @hide + */ + public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN"; + + /** + * 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"; + + + private static int sMaxRecentTasks = -1; + + ActivityTaskManager(Context context, Handler handler) { + } + + /** @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); + } + }; + + /** + * Sets the windowing mode for a specific task. Only works on tasks of type + * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} + * @param taskId The id of the task to set the windowing mode for. + * @param windowingMode The windowing mode to set for the task. + * @param toTop If the task should be moved to the top once the windowing mode changes. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) + throws SecurityException { + try { + getService().setTaskWindowingMode(taskId, windowingMode, toTop); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Moves the input task to the primary-split-screen stack. + * @param taskId Id of task to move. + * @param createMode The mode the primary split screen stack should be created in if it doesn't + * exist already. See + * {@link ActivityTaskManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT} + * and + * {@link android.app.ActivityManager + * #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT} + * @param toTop If the task and stack should be moved to the top. + * @param animate Whether we should play an animation for the moving the task + * @param initialBounds If the primary stack gets created, it will use these bounds for the + * docked stack. Pass {@code null} to use default bounds. + * @param showRecents If the recents activity should be shown on the other side of the task + * going into split-screen mode. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop, + boolean animate, Rect initialBounds, boolean showRecents) throws SecurityException { + try { + getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate, + initialBounds, showRecents); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resizes the input stack id to the given bounds. + * @param stackId Id of the stack to resize. + * @param bounds Bounds to resize the stack to or {@code null} for fullscreen. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void resizeStack(int stackId, Rect bounds) throws SecurityException { + try { + getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */, + false /* preserveWindows */, false /* animate */, -1 /* animationDuration */); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes stacks 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_STACKS) + public void removeStacksInWindowingModes(int[] windowingModes) throws SecurityException { + try { + getService().removeStacksInWindowingModes(windowingModes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Removes stack of the activity types from the system. */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void removeStacksWithActivityTypes(int[] activityTypes) throws SecurityException { + try { + getService().removeStacksWithActivityTypes(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; + } + + /** + * 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 true if the system supports split screen multi-window. */ + public static boolean supportsSplitScreenMultiWindow(Context context) { + return supportsMultiWindow(context) + && Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_supportsSplitScreenMultiWindow); + } + + /** + * Moves the top activity in the input stackId to the pinned stack. + * @param stackId Id of stack to move the top activity to pinned stack. + * @param bounds Bounds to use for pinned stack. + * @return True if the top activity of stack was successfully moved to the pinned stack. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) { + try { + return getService().moveTopActivityToPinnedStack(stackId, bounds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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_STACKS) + 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_STACKS) + public void stopSystemLockTaskMode() { + try { + getService().stopSystemLockTaskMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Move task to stack with given id. + * @param taskId Id of the task to move. + * @param stackId Id of the stack for task moving. + * @param toTop Whether the given task should shown to top of stack. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void moveTaskToStack(int taskId, int stackId, boolean toTop) { + try { + getService().moveTaskToStack(taskId, stackId, toTop); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resize the input stack id to the given bounds with animate setting. + * @param stackId Id of the stack to resize. + * @param bounds Bounds to resize the stack to or {@code null} for fullscreen. + * @param animate Whether we should play an animation for resizing stack. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void resizeStack(int stackId, Rect bounds, boolean animate) { + try { + getService().resizeStack(stackId, bounds, false, false, animate /* animate */, + -1 /* animationDuration */); + } 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_STACKS) + public void resizeTask(int taskId, Rect bounds) { + try { + getService().resizeTask(taskId, bounds, RESIZE_MODE_SYSTEM); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resize docked stack & its task to given stack & task bounds. + * @param stackBounds Bounds to resize stack. + * @param taskBounds Bounds to resize task. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void resizeDockedStack(Rect stackBounds, Rect taskBounds) { + try { + getService().resizeDockedStack(stackBounds, taskBounds, null, null, null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * List all activity stacks information. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public String listAllStacks() { + final List<ActivityManager.StackInfo> stacks; + try { + stacks = getService().getAllStackInfos(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + final StringBuilder sb = new StringBuilder(); + if (stacks != null) { + for (ActivityManager.StackInfo info : stacks) { + sb.append(info).append("\n"); + } + } + return sb.toString(); + } + + /** + * 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_STACKS) + public void clearLaunchParamsForPackages(List<String> packageNames) { + try { + getService().clearLaunchParamsForPackages(packageNames); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Makes the display with the given id a single task instance display. I.e the display can only + * contain one task. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void setDisplayToSingleTaskInstance(int displayId) { + try { + getService().setDisplayToSingleTaskInstance(displayId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +}
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java new file mode 100644 index 0000000..546f000 --- /dev/null +++ b/android/app/ActivityThread.java
@@ -0,0 +1,7371 @@ +/* + * 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.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.INVALID_DISPLAY; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; +import android.app.backup.BackupAgent; +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.PendingTransactionActions; +import android.app.servertransaction.PendingTransactionActions.StopInfo; +import android.app.servertransaction.TransactionExecutor; +import android.app.servertransaction.TransactionExecutorHelper; +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.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.ProviderInfo; +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.Resources.Theme; +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.ImageDecoder; +import android.hardware.display.DisplayManagerGlobal; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.Proxy; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +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.StrictMode; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +import android.provider.BlockedNumberContract; +import android.provider.CalendarContract; +import android.provider.CallLog; +import android.provider.ContactsContract; +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.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.SparseIntArray; +import android.util.SuperNotCalledException; +import android.util.UtilConfig; +import android.util.proto.ProtoOutputStream; +import android.view.Choreographer; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.ThreadedRenderer; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewManager; +import android.view.ViewRootImpl; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.webkit.WebView; + +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.BinderInternal; +import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SomeArgs; +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.OpenSSLSocketImpl; +import com.android.org.conscrypt.TrustedCertificateStore; +import com.android.server.am.MemInfoDumpProto; + +import dalvik.system.CloseGuard; +import dalvik.system.VMDebug; +import dalvik.system.VMRuntime; + +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.Files; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.TimeZone; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +final class RemoteServiceException extends AndroidRuntimeException { + public RemoteServiceException(String msg) { + super(msg); + } +} + +/** + * 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 { + /** @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; + /** + * If the activity doesn't become idle in time, the timeout will ensure to apply the pending top + * process state. + */ + private static final long PENDING_TOP_PROCESS_STATE_TIMEOUT = 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; + + // Whether to invoke an activity callback after delivering new configuration. + private static final boolean REPORT_TO_ACTIVITY = true; + + /** 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; + + /** + * 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(); + + /** + * 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; + private ContextImpl mSystemUiContext; + + @UnsupportedAppUsage + static volatile IPackageManager sPackageManager; + + @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<>(); + /** The activities to be truly destroyed (not include relaunch). */ + final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = + Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); + // List of new activities (via ActivityRecord.nextIdle) that should + // be reported when next we idle. + ActivityClientRecord mNewActivities = null; + // 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; + @GuardedBy("mAppThread") + private int mPendingProcessState = PROCESS_STATE_UNKNOWN; + ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>(); + private int mLastSessionId; + @UnsupportedAppUsage + final ArrayMap<IBinder, Service> mServices = new ArrayMap<>(); + @UnsupportedAppUsage + AppBindData mBoundApplication; + Profiler mProfiler; + @UnsupportedAppUsage + int mCurDefaultDisplayDpi; + @UnsupportedAppUsage + boolean mDensityCompatMode; + @UnsupportedAppUsage + Configuration mConfiguration; + Configuration mCompatConfiguration; + @UnsupportedAppUsage + Application mInitialApplication; + @UnsupportedAppUsage + final ArrayList<Application> mAllApplications + = new ArrayList<Application>(); + /** + * 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 mSystemThread = false; + boolean mSomeActivitiesChanged = false; + boolean mUpdatingSystemConfig = false; + /* package */ boolean mHiddenApiWarningShown = 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 + 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; + + public ProviderKey(String authority, int userId) { + this.authority = authority; + this.userId = userId; + } + + @Override + public boolean equals(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. + // TODO Remove it once we move CPR.wait() from AMS to the client side. + @GuardedBy("mGetProviderLocks") + final ArrayMap<ProviderKey, Object> mGetProviderLocks = new ArrayMap<>(); + + final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners + = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); + + 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() + + Bundle mCoreSettings = null; + + /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */ + public static final class ActivityClientRecord { + @UnsupportedAppUsage + public IBinder token; + public IBinder assistToken; + 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 newConfig; + Configuration createdConfig; + Configuration overrideConfig; + // Used to save the last reported configuration from server side so that activity + // configuration transactions can always use the latest configuration. + @GuardedBy("this") + private Configuration mPendingOverrideConfig; + // Used for consolidating configs before sending on to Activity. + private Configuration tmpConfig = new Configuration(); + // Callback used for updating activity override config. + ViewRootImpl.ActivityConfigCallback configCallback; + ActivityClientRecord nextIdle; + + // 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; + + Window mPendingRemoveWindow; + WindowManager mPendingRemoveWindowManager; + @UnsupportedAppUsage + boolean mPreserveWindow; + + @LifecycleState + private int mLifecycleState = PRE_ON_CREATE; + + @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, CompatibilityInfo compatInfo, + String referrer, IVoiceInteractor voiceInteractor, Bundle state, + PersistableBundle persistentState, List<ResultInfo> pendingResults, + List<ReferrerIntent> pendingNewIntents, boolean isForward, + ProfilerInfo profilerInfo, ClientTransactionHandler client, + IBinder assistToken) { + this.token = token; + this.assistToken = assistToken; + this.ident = ident; + this.intent = intent; + this.referrer = referrer; + this.voiceInteractor = voiceInteractor; + this.activityInfo = info; + this.compatInfo = compatInfo; + 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, + compatInfo); + init(); + } + + /** Common initializer for all constructors. */ + private void init() { + parent = null; + embeddedID = null; + paused = false; + stopped = false; + hideForNow = false; + nextIdle = null; + configCallback = (Configuration overrideConfig, int newDisplayId) -> { + if (activity == null) { + throw new IllegalStateException( + "Received config update for non-existing activity"); + } + activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig, + newDisplayId); + }; + } + + /** 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, IBinder token, int sendingUser) { + super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, + token, sendingUser, intent.getFlags()); + 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) + "}"; + } + } + + static final class CreateBackupAgentData { + ApplicationInfo appInfo; + CompatibilityInfo compatInfo; + int backupMode; + int userId; + public String toString() { + return "CreateBackupAgentData{appInfo=" + appInfo + + " backupAgent=" + appInfo.backupAgentName + + " mode=" + backupMode + " userId=" + userId + "}"; + } + } + + static final class 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; + public String toString() { + return "BindServiceData{token=" + token + " intent=" + intent + "}"; + } + } + + 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 + LoadedApk info; + @UnsupportedAppUsage + String processName; + @UnsupportedAppUsage + ApplicationInfo appInfo; + @UnsupportedAppUsage + List<ProviderInfo> providers; + ComponentName instrumentationName; + @UnsupportedAppUsage + Bundle instrumentationArgs; + IInstrumentationWatcher instrumentationWatcher; + IUiAutomationConnection instrumentationUiAutomationConnection; + int debugMode; + boolean enableBinderTracking; + boolean trackAllocation; + @UnsupportedAppUsage + boolean restrictedBackupMode; + @UnsupportedAppUsage + 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; + + @Override + public String toString() { + return "AppBindData{appInfo=" + appInfo + "}"; + } + } + + static final class Profiler { + String profileFile; + ParcelFileDescriptor profileFd; + int samplingInterval; + boolean autoStopProfiler; + boolean streamingOutput; + 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; + } + 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, 0, 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 UpdateCompatibilityData { + String pkg; + CompatibilityInfo info; + } + + static final class RequestAssistContextExtras { + IBinder activityToken; + IBinder requestToken; + int requestType; + int sessionId; + int flags; + } + + private class ApplicationThread extends IApplicationThread.Stub { + private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; + + public final void scheduleSleeping(IBinder token, boolean sleeping) { + sendMessage(H.SLEEPING, token, sleeping ? 1 : 0); + } + + public final void scheduleReceiver(Intent intent, ActivityInfo info, + CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, + boolean sync, int sendingUser, int processState) { + updateProcessState(processState, false); + ReceiverData r = new ReceiverData(intent, resultCode, data, extras, + sync, false, mAppThread.asBinder(), sendingUser); + r.info = info; + r.compatInfo = compatInfo; + sendMessage(H.RECEIVER, r); + } + + public final void scheduleCreateBackupAgent(ApplicationInfo app, + CompatibilityInfo compatInfo, int backupMode, int userId) { + CreateBackupAgentData d = new CreateBackupAgentData(); + d.appInfo = app; + d.compatInfo = compatInfo; + d.backupMode = backupMode; + d.userId = userId; + + sendMessage(H.CREATE_BACKUP_AGENT, d); + } + + public final void scheduleDestroyBackupAgent(ApplicationInfo app, + CompatibilityInfo compatInfo, int userId) { + CreateBackupAgentData d = new CreateBackupAgentData(); + d.appInfo = app; + d.compatInfo = compatInfo; + 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; + s.compatInfo = compatInfo; + + sendMessage(H.CREATE_SERVICE, s); + } + + public final void scheduleBindService(IBinder token, Intent intent, + boolean rebind, int processState) { + updateProcessState(processState, false); + BindServiceData s = new BindServiceData(); + s.token = token; + s.intent = intent; + s.rebind = rebind; + + 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; + + 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); + } + + public final void bindApplication(String processName, ApplicationInfo appInfo, + List<ProviderInfo> providers, 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) { + 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.providers = providers; + 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; + sendMessage(H.BIND_APPLICATION, data); + } + + 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) { + 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().onNetworkConfigurationChanged(); + } + + public void updateHttpProxy() { + ActivityThread.updateHttpProxy( + getApplication() != null ? getApplication() : getSystemContext()); + } + + 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, int sendingUser, int processState) throws RemoteException { + updateProcessState(processState, false); + 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; + } + dhd.finishCallback = finishCallback; + sendMessage(H.DUMP_HEAP, dhd, 0, 0, true /*async*/); + } + + public void attachAgent(String agent) { + sendMessage(H.ATTACH_AGENT, agent); + } + + 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); + } + + public void scheduleCrash(String msg) { + sendMessage(H.SCHEDULE_CRASH, msg); + } + + 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, + OpenSSLSocketImpl.class + }; + long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true); + long appContextInstanceCount = instanceCounts[0]; + long activityInstanceCount = instanceCounts[1]; + long webviewInstanceCount = instanceCounts[2]; + long openSslSocketCount = instanceCounts[3]; + + long viewInstanceCount = ViewDebug.getViewInstanceCount(); + long viewRootInstanceCount = ViewDebug.getViewRootImplCount(); + 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(','); + pw.print(openSslSocketCount); 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.cache); + pw.print(','); pw.print(dbStats.cache); + } + 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, + "OpenSSL Sockets:", openSslSocketCount); + printRow(pw, ONE_COUNT_COLUMN, "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_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache", + "Dbname"); + for (int i = 0; i < N; i++) { + DbStats dbStats = stats.dbStats.get(i); + printRow(pw, DB_INFO_FORMAT, + (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ", + (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ", + (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ", + dbStats.cache, 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, + OpenSSLSocketImpl.class + }; + long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true); + long appContextInstanceCount = instanceCounts[0]; + long activityInstanceCount = instanceCounts[1]; + long webviewInstanceCount = instanceCounts[2]; + long openSslSocketCount = instanceCounts[3]; + + long viewInstanceCount = ViewDebug.getViewInstanceCount(); + long viewRootInstanceCount = ViewDebug.getViewRootImplCount(); + 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.OPEN_SSL_SOCKET_COUNT, + openSslSocketCount); + 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, dbStats.cache); + 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) { + nDumpGraphicsInfo(pfd.getFileDescriptor()); + WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), 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; + 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)); + } + + @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)); + } + } + + 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(); + } + } + } + } + + 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; + + 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 SLEEPING: return "SLEEPING"; + 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"; + } + } + 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: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp"); + handleReceiver((ReceiverData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case CREATE_SERVICE: + 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: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind"); + handleBindService((BindServiceData)msg.obj); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case UNBIND_SERVICE: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind"); + handleUnbindService((BindServiceData)msg.obj); + schedulePurgeIdler(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case SERVICE_ARGS: + 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: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop"); + handleStopService((IBinder)msg.obj); + schedulePurgeIdler(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + case CONFIGURATION_CHANGED: + 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: + scheduleGcIdler(); + break; + case DUMP_SERVICE: + handleDumpService((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: + throw new RemoteServiceException((String)msg.obj); + case DUMP_HEAP: + handleDumpHeap((DumpHeapData) msg.obj); + break; + case DUMP_ACTIVITY: + handleDumpActivity((DumpComponentInfo)msg.obj); + break; + case DUMP_PROVIDER: + handleDumpProvider((DumpComponentInfo)msg.obj); + break; + case SLEEPING: + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "sleeping"); + handleSleeping((IBinder)msg.obj, msg.arg1 != 0); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + 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: + handleInstallProvider((ProviderInfo) msg.obj); + 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: + mUpdatingSystemConfig = true; + try { + handleApplicationInfoChanged((ApplicationInfo) msg.obj); + } finally { + mUpdatingSystemConfig = false; + } + 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; + } + 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() { + ActivityClientRecord a = mNewActivities; + boolean stopProfiling = false; + if (mBoundApplication != null && mProfiler.profileFd != null + && mProfiler.autoStopProfiler) { + stopProfiling = true; + } + if (a != null) { + mNewActivities = null; + IActivityTaskManager am = ActivityTaskManager.getService(); + ActivityClientRecord prev; + do { + if (localLOGV) Slog.v( + TAG, "Reporting idle of " + a + + " finished=" + + (a.activity != null && a.activity.mFinished)); + if (a.activity != null && !a.activity.mFinished) { + try { + am.activityIdle(a.token, a.createdConfig, stopProfiling); + a.createdConfig = null; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + prev = a; + a = a.nextIdle; + prev.nextIdle = null; + } while (a != null); + } + if (stopProfiling) { + mProfiler.stopProfiling(); + } + applyPendingProcessState(); + 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; + } + + @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) { + //Slog.v("PackageManager", "returning cur default = " + sPackageManager); + return sPackageManager; + } + IBinder b = ServiceManager.getService("package"); + //Slog.v("PackageManager", "default service binder = " + b); + sPackageManager = IPackageManager.Stub.asInterface(b); + //Slog.v("PackageManager", "default service = " + sPackageManager); + return sPackageManager; + } + + private Configuration mMainThreadConfig = new Configuration(); + + Configuration applyConfigCompatMainThread(int displayDensity, Configuration config, + CompatibilityInfo compat) { + if (config == null) { + return null; + } + if (!compat.supportsScreen()) { + mMainThreadConfig.setTo(config); + config = mMainThreadConfig; + compat.applyToConfiguration(displayDensity, config); + } + return config; + } + + /** + * 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[] overlayDirs, + String[] libDirs, int displayId, LoadedApk pkgInfo) { + return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, + displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader()); + } + + @UnsupportedAppUsage + final 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; + try { + ai = getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_SHARED_LIBRARY_FILES + | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + (userId < 0) ? UserHandle.myUserId() : userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + 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)) { + packageInfo.updateApplicationInfo(ai, null); + } + + 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 + 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); + } + + @Override + @UnsupportedAppUsage + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, + CompatibilityInfo compatInfo) { + return getPackageInfo(ai, compatInfo, null, false, true, false); + } + + @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) { + final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); + synchronized (mResourcesManager) { + WeakReference<LoadedApk> ref; + if (differentUser) { + // Caching not supported across users + 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)) { + packageInfo.updateApplicationInfo(aInfo, null); + } + + 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) { + // Caching not supported across users + } 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; + String[] overlayDirs = ArrayUtils.defeatNullable(loadedApk.getOverlayDirs()); + String[] resourceDirs = ArrayUtils.defeatNullable(appInfo.resourceDirs); + + return (packageResources == null || packageResources.getAssets().isUpToDate()) + && overlayDirs.length == resourceDirs.length + && ArrayUtils.containsAll(overlayDirs, resourceDirs); + } + + @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; + } + + @UnsupportedAppUsage + public Application getApplication() { + return mInitialApplication; + } + + @UnsupportedAppUsage + public String getProcessName() { + return mBoundApplication.processName; + } + + @UnsupportedAppUsage + public ContextImpl getSystemContext() { + synchronized (this) { + if (mSystemContext == null) { + mSystemContext = ContextImpl.createSystemContext(this); + } + return mSystemContext; + } + } + + public ContextImpl getSystemUiContext() { + synchronized (this) { + if (mSystemUiContext == null) { + mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext()); + } + return mSystemUiContext; + } + } + + /** + * Create 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) + */ + public ContextImpl createSystemUiContext(int displayId) { + return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId); + } + + 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"; + private static final String HEAP_COLUMN + = "%13s %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 ONE_COUNT_COLUMN_HEADER = "%21s %8s"; + + // 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", + "Heap", "Heap", "Heap"); + printRow(pw, HEAP_FULL_COLUMN, "", "Total", "Clean", "Dirty", "Dirty", + "Clean", "Clean", "Dirty", + "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, + 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, + dalvikMax, dalvikAllocated, dalvikFree); + } else { + printRow(pw, HEAP_COLUMN, "", "Pss", "Private", + "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap", + "Heap", "Heap", "Heap"); + printRow(pw, HEAP_COLUMN, "", "Total", "Dirty", + "Clean", "Dirty", "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, + nativeMax, nativeAllocated, nativeFree); + printRow(pw, HEAP_COLUMN, "Dalvik Heap", memInfo.dalvikPss, + memInfo.dalvikPrivateDirty, + memInfo.dalvikPrivateClean, + memInfo.hasSwappedOutPss ? memInfo.dalvikSwappedOutPss : + memInfo.dalvikSwappedOut, + 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; + + 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); + 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, + "", "", ""); + } else { + printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i), + myPss, myPrivateDirty, + myPrivateClean, + memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut, + "", "", ""); + } + otherPss -= myPss; + otherSwappablePss -= mySwappablePss; + otherSharedDirty -= mySharedDirty; + otherPrivateDirty -= myPrivateDirty; + otherSharedClean -= mySharedClean; + otherPrivateClean -= myPrivateClean; + otherSwappedOut -= mySwappedOut; + otherSwappedOutPss -= mySwappedOutPss; + } + } + + if (dumpFullInfo) { + printRow(pw, HEAP_FULL_COLUMN, "Unknown", otherPss, otherSwappablePss, + otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean, + memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut, + "", "", ""); + printRow(pw, HEAP_FULL_COLUMN, "TOTAL", memInfo.getTotalPss(), + memInfo.getTotalSwappablePss(), + memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(), + memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(), + memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() : + memInfo.getTotalSwappedOut(), + nativeMax+dalvikMax, nativeAllocated+dalvikAllocated, + nativeFree+dalvikFree); + } else { + printRow(pw, HEAP_COLUMN, "Unknown", otherPss, + otherPrivateDirty, otherPrivateClean, + memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut, + "", "", ""); + printRow(pw, HEAP_COLUMN, "TOTAL", memInfo.getTotalPss(), + memInfo.getTotalPrivateDirty(), + memInfo.getTotalPrivateClean(), + memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() : + memInfo.getTotalSwappedOut(), + 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); + 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, + "", "", ""); + } else { + printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i), + myPss, myPrivateDirty, + myPrivateClean, + memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut, + "", "", ""); + } + } + } + } + } + + pw.println(" "); + pw.println(" App Summary"); + printRow(pw, ONE_COUNT_COLUMN_HEADER, "", "Pss(KB)"); + printRow(pw, ONE_COUNT_COLUMN_HEADER, "", "------"); + printRow(pw, ONE_COUNT_COLUMN, + "Java Heap:", memInfo.getSummaryJavaHeap()); + printRow(pw, ONE_COUNT_COLUMN, + "Native Heap:", memInfo.getSummaryNativeHeap()); + printRow(pw, ONE_COUNT_COLUMN, + "Code:", memInfo.getSummaryCode()); + printRow(pw, ONE_COUNT_COLUMN, + "Stack:", memInfo.getSummaryStack()); + printRow(pw, ONE_COUNT_COLUMN, + "Graphics:", memInfo.getSummaryGraphics()); + printRow(pw, ONE_COUNT_COLUMN, + "Private Other:", memInfo.getSummaryPrivateOther()); + printRow(pw, ONE_COUNT_COLUMN, + "System:", memInfo.getSummarySystem()); + pw.println(" "); + if (memInfo.hasSwappedOutPss) { + printRow(pw, TWO_COUNT_COLUMNS, + "TOTAL:", memInfo.getSummaryTotalPss(), + "TOTAL SWAP PSS:", memInfo.getSummaryTotalSwapPss()); + } else { + printRow(pw, TWO_COUNT_COLUMNS, + "TOTAL:", memInfo.getSummaryTotalPss(), + "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) { + 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.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); + 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); + 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; + + 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); + if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 + || mySharedClean != 0 || myPrivateClean != 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); + + otherPss -= myPss; + otherSwappablePss -= mySwappablePss; + otherSharedDirty -= mySharedDirty; + otherPrivateDirty -= myPrivateDirty; + otherSharedClean -= mySharedClean; + otherPrivateClean -= myPrivateClean; + otherSwappedOut -= mySwappedOut; + otherSwappedOutPss -= mySwappedOutPss; + } + } + + dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.UNKNOWN_HEAP, "Unknown", + otherPss, otherSwappablePss, + otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean, + memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss); + 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()); + 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); + 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); + } + } + } + } + + 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.end(asToken); + } + + @UnsupportedAppUsage + 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 + 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) { + ActivityClientRecord r = new ActivityClientRecord(); + r.token = token; + r.assistToken = assistToken; + 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); + } + + @Override + public void updatePendingConfiguration(Configuration config) { + synchronized (mResourcesManager) { + if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(config)) { + mPendingConfiguration = config; + } + } + } + + @Override + public void updateProcessState(int processState, boolean fromIpc) { + synchronized (mAppThread) { + if (mLastProcessState == processState) { + return; + } + mLastProcessState = processState; + // Defer the top state for VM to avoid aggressive JIT compilation affecting activity + // launch time. + if (processState == ActivityManager.PROCESS_STATE_TOP + && mNumLaunchingActivities.get() > 0) { + mPendingProcessState = processState; + mH.postDelayed(this::applyPendingProcessState, PENDING_TOP_PROCESS_STATE_TIMEOUT); + } else { + mPendingProcessState = PROCESS_STATE_UNKNOWN; + updateVmProcessState(processState); + } + if (localLOGV) { + Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState + + (fromIpc ? " (from ipc" : "")); + } + } + } + + /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */ + private void updateVmProcessState(int processState) { + // TODO: Tune this since things like gmail sync are important background but not jank + // perceptible. + final int state = processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + ? VM_PROCESS_STATE_JANK_PERCEPTIBLE + : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE; + VMRuntime.getRuntime().updateProcessState(state); + } + + private void applyPendingProcessState() { + synchronized (mAppThread) { + if (mPendingProcessState == PROCESS_STATE_UNKNOWN) { + return; + } + final int pendingState = mPendingProcessState; + mPendingProcessState = PROCESS_STATE_UNKNOWN; + // Only apply the pending state if the last state doesn't change. + if (pendingState == mLastProcessState) { + updateVmProcessState(pendingState); + } + } + } + + @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); + } + + private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) { + if (DEBUG_MESSAGES) Slog.v( + TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 + + "seq= " + seq); + Message msg = Message.obtain(); + msg.what = what; + SomeArgs args = SomeArgs.obtain(); + args.arg1 = obj; + args.argi1 = arg1; + args.argi2 = arg2; + args.argi3 = seq; + msg.obj = args; + 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, r.compatInfo, + 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(); + 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.makeApplication(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()); + + if (activity != null) { + CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); + Configuration config = new Configuration(mCompatConfiguration); + 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; + } + 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.configCallback, + r.assistToken); + + if (customIntent != null) { + activity.mIntent = customIntent; + } + r.lastNonConfigurationInstances = null; + checkAndBlockForNetworkAccess(); + activity.mStartedActivity = false; + int theme = r.activityInfo.getThemeResource(); + if (theme != 0) { + activity.setTheme(theme); + } + + activity.mCalled = false; + 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.activity = activity; + } + r.setState(ON_CREATE); + + // 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); + } + + } 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) { + final Activity activity = r.activity; + if (r.activity == null) { + // TODO(lifecycler): What do we do in this case? + return; + } + 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; + } + + // 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; + if (r.isPersistable()) { + mInstrumentation.callActivityOnPostCreate(activity, r.state, + r.persistentState); + } else { + mInstrumentation.callActivityOnPostCreate(activity, r.state); + } + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPostCreate()"); + } + } + } + + /** + * 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; + try { + displayId = ActivityTaskManager.getService().getActivityDisplayId(r.token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + 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 != Display.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, 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. + handleConfigurationChanged(null, null); + + if (localLOGV) Slog.v( + TAG, "Handling launch of " + r); + + // Initialize before creating the activity + if (!ThreadedRenderer.sRendererDisabled + && (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(mConfiguration); + 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. + try { + ActivityTaskManager.getService() + .finishActivity(r.token, Activity.RESULT_CANCELED, null, + Activity.DONT_FINISH_TASK_WITH_ACTIVITY); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + 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; + } + SparseIntArray horizontal = new SparseIntArray(); + SparseIntArray vertical = new SparseIntArray(); + SparseIntArray smallest = new SparseIntArray(); + for (int i = configurations.length - 1; i >= 0; i--) { + Configuration config = configurations[i]; + if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + vertical.put(config.screenHeightDp, 0); + } + if (config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + horizontal.put(config.screenWidthDp, 0); + } + if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + smallest.put(config.smallestScreenWidthDp, 0); + } + } + try { + ActivityTaskManager.getService().reportSizeConfigurations(r.token, + horizontal.copyKeys(), vertical.copyKeys(), smallest.copyKeys()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + 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(); + r.activity.mFragments.noteStateNotSaved(); + mInstrumentation.callActivityOnNewIntent(r.activity, intent); + } + } + + @Override + public void handleNewIntent(IBinder token, List<ReferrerIntent> intents) { + final ActivityClientRecord r = mActivities.get(token); + if (r == null) { + return; + } + + 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; + + // 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) { + 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)); + intent.removeUnsafeExtras(); + content.setDefaultIntent(intent); + } + } else { + if (!forAutofill) { + content.setDefaultIntent(new Intent()); + } + } + if (!forAutofill) { + r.activity.onProvideAssistContent(content); + } + } + + } + 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) { + 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 || 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) -> { + Preconditions.checkNotNull(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.enableTracing(); + } + + private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) { + try { + Binder.disableTracing(); + Binder.getTransactionTracker().writeTracesToFile(fd); + } finally { + IoUtils.closeQuietly(fd); + Binder.getTransactionTracker().clearTraces(); + } + } + + @Override + public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, + Configuration overrideConfig) { + final ActivityClientRecord r = mActivities.get(token); + if (r != null) { + final Configuration newConfig = new Configuration(mConfiguration); + if (overrideConfig != null) { + newConfig.updateFrom(overrideConfig); + } + r.activity.dispatchMultiWindowModeChanged(isInMultiWindowMode, newConfig); + } + } + + @Override + public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode, + Configuration overrideConfig) { + final ActivityClientRecord r = mActivities.get(token); + if (r != null) { + final Configuration newConfig = new Configuration(mConfiguration); + if (overrideConfig != null) { + newConfig.updateFrom(overrideConfig); + } + r.activity.dispatchPictureInPictureModeChanged(isInPipMode, newConfig); + } + } + + 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); + } + } + + 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(); + + LoadedApk packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo, data.compatInfo); + + IActivityManager mgr = ActivityManager.getService(); + + Application app; + BroadcastReceiver receiver; + ContextImpl context; + try { + app = packageInfo.makeApplication(false, mInstrumentation); + context = (ContextImpl) app.getBaseContext(); + if (data.info.splitName != null) { + context = (ContextImpl) context.createContextForSplit(data.info.splitName); + } + java.lang.ClassLoader cl = context.getClassLoader(); + data.intent.setExtrasClassLoader(cl); + data.intent.prepareToEnterProcess(); + 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); + + // Sanity 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 + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; + if (packageName == null) { + Slog.d(TAG, "Asked to create backup agent for nonexistent package"); + return; + } + + String classname = data.appInfo.backupAgentName; + // full backup operation but no app-supplied agent? use the default implementation + if (classname == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL + || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) { + classname = "android.app.backup.FullBackupAgent"; + } + + 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)); + 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); + } + } + + // Tear down a BackupAgent + private void handleDestroyBackupAgent(CreateBackupAgentData data) { + if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); + + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + 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(); + + LoadedApk packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo, data.compatInfo); + Service service = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = packageInfo.getAppFactory() + .instantiateService(cl, data.info.name, data.intent); + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to instantiate service " + data.info.name + + ": " + e.toString(), e); + } + } + + try { + if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); + + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); + context.setOuterContext(service); + + Application app = packageInfo.makeApplication(false, mInstrumentation); + service.attach(context, this, data.info.name, data.token, app, + ActivityManager.getService()); + service.onCreate(); + 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) { + 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(); + 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) { + Service s = mServices.get(data.token); + if (s != null) { + try { + data.intent.setExtrasClassLoader(s.getClassLoader()); + data.intent.prepareToEnterProcess(); + 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 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 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.dump(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) { + Service s = mServices.get(data.token); + if (s != null) { + try { + if (data.args != null) { + data.args.setExtrasClassLoader(s.getClassLoader()); + data.args.prepareToEnterProcess(); + } + 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) { + 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); + } + + /** + * Resume the activity. + * @param token Target activity token. + * @param finalStateRequest Flag indicating if this is part of final state resolution for a + * transaction. + * @param reason Reason for performing the action. + * + * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise. + */ + @VisibleForTesting + public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest, + String reason) { + final ActivityClientRecord r = mActivities.get(token); + if (localLOGV) { + Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); + } + if (r == null || r.activity.mFinished) { + return null; + } + 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 null; + } + 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 r; + } + + 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(IBinder token, boolean finalStateRequest, boolean isForward, + 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 + final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); + if (r == null) { + // We didn't actually resume the activity, so skipping any follow-up actions. + return; + } + if (mActivitiesToBeDestroyed.containsKey(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) { + try { + willBeVisible = ActivityTaskManager.getService().willActivityBeVisible( + a.getActivityToken()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + 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 (r.newConfig != null) { + performConfigurationChangedForActivity(r, r.newConfig); + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + + r.activity.mCurrentConfig); + } + r.newConfig = null; + } + if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); + WindowManager.LayoutParams l = 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(); + } + } + + r.nextIdle = mNewActivities; + mNewActivities = r; + if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); + Looper.myQueue().addIdleHandler(new Idler()); + } + + + @Override + public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) { + ActivityClientRecord r = mActivities.get(token); + if (r == null || r.activity == null) { + Slog.w(TAG, "Not found target activity to report position change for token: " + token); + return; + } + + if (DEBUG_ORDER) { + Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r); + } + + if (r.isTopResumedActivity == onTop) { + throw new IllegalStateException("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(IBinder token, boolean finished, boolean userLeaving, + int configChanges, PendingTransactionActions pendingActions, String reason) { + ActivityClientRecord r = mActivities.get(token); + if (r != null) { + if (userLeaving) { + performUserLeavingActivity(r); + } + + r.activity.mConfigChangeFlags |= configChanges; + 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.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); + } + + /** Called from {@link LocalActivityManager}. */ + @UnsupportedAppUsage + final void performStopActivity(IBinder token, boolean saveState, String reason) { + ActivityClientRecord r = mActivities.get(token); + performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, 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. Note this is a little + * tricky because the server's meaning of stop is slightly different + * than our client -- for the server, stop means to save state and give + * it the result when it is done, but the window may still be visible. + * For the client, we want to call onStop()/onStart() to indicate when + * the activity's UI visibility changes. + * @param r Target activity client record. + * @param info Action that will report activity stop to server. + * @param keepShown Flag indicating whether the activity is still shown. + * @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 keepShown, + boolean saveState, boolean finalStateRequest, String reason) { + if (localLOGV) Slog.v(TAG, "Performing stop of " + r); + if (r != null) { + if (!keepShown && 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); + } + } + } + + if (!keepShown) { + 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(); + } + } + if (r.newConfig != null) { + performConfigurationChangedForActivity(r, r.newConfig); + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis " + + r.activityInfo.name + " with new config " + + r.activity.mCurrentConfig); + r.newConfig = null; + } + } else { + if (r.activity.mVisibleFromServer) { + r.activity.mVisibleFromServer = false; + mNumVisibleActivities--; + v.setVisibility(View.INVISIBLE); + } + } + } + } + + @Override + public void handleStopActivity(IBinder token, boolean show, int configChanges, + PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { + final ActivityClientRecord r = mActivities.get(token); + r.activity.mConfigChangeFlags |= configChanges; + + final StopInfo stopInfo = new StopInfo(); + performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest, + reason); + + if (localLOGV) Slog.v( + TAG, "Finishing stop of " + r + ": show=" + show + + " win=" + r.window); + + updateVisibility(r, show); + + // 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(IBinder token, boolean start) { + ActivityClientRecord r = mActivities.get(token); + if (r.stopped) { + r.activity.performRestart(start, "performRestartActivity"); + if (start) { + r.setState(ON_START); + } + } + } + + @Override + public void handleWindowVisibility(IBinder token, boolean show) { + ActivityClientRecord r = mActivities.get(token); + + if (r == null) { + Log.w(TAG, "handleWindowVisibility: no activity for token " + token); + return; + } + + if (!show && !r.stopped) { + performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */, + false /* finalStateRequest */, "handleWindowVisibility"); + } else if (show && r.getLifecycleState() == ON_STOP) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + r.activity.performRestart(true /* start */, "handleWindowVisibility"); + r.setState(ON_START); + } + if (r.activity.mDecor != null) { + if (false) Slog.v( + TAG, "Handle window " + r + " visibility: " + show); + updateVisibility(r, show); + } + mSomeActivitiesChanged = true; + } + + // TODO: This method should be changed to use {@link #performStopActivityInner} to perform to + // stop operation on the activity to reduce code duplication and the chance of fixing a bug in + // one place and missing the other. + private void handleSleeping(IBinder token, boolean sleeping) { + ActivityClientRecord r = mActivities.get(token); + + if (r == null) { + Log.w(TAG, "handleSleeping: no activity for token " + token); + return; + } + + if (sleeping) { + if (!r.stopped && !r.isPreHoneycomb()) { + callActivityOnStop(r, true /* saveState */, "sleeping"); + } + + // Make sure any pending writes are now committed. + if (!r.isPreHoneycomb()) { + QueuedWork.waitToFinish(); + } + + // Tell activity manager we slept. + try { + ActivityTaskManager.getService().activitySlept(r.token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } else { + if (r.stopped && r.activity.mVisibleFromServer) { + r.activity.performRestart(true /* start */, "handleSleeping"); + r.setState(ON_START); + } + } + } + + private void handleSetCoreSettings(Bundle coreSettings) { + synchronized (mResourcesManager) { + mCoreSettings = coreSettings; + } + onCoreSettingsChange(); + } + + private void onCoreSettingsChange() { + if (updateDebugViewAttributeState()) { + // request all activities to relaunch for the changes to take place + relaunchAllActivities(false /* preserveWindows */); + } + } + + private boolean updateDebugViewAttributeState() { + boolean previousState = View.sDebugViewAttributes; + + View.sDebugViewAttributesApplicationPackage = mCoreSettings.getString( + Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, ""); + String currentPackage = (mBoundApplication != null && mBoundApplication.appInfo != null) + ? mBoundApplication.appInfo.packageName : ""; + View.sDebugViewAttributes = + mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0 + || View.sDebugViewAttributesApplicationPackage.equals(currentPackage); + return previousState != View.sDebugViewAttributes; + } + + private void relaunchAllActivities(boolean preserveWindows) { + for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { + final ActivityClientRecord r = entry.getValue(); + if (!r.activity.mFinished) { + if (preserveWindows && r.window != null) { + r.mPreserveWindow = true; + } + scheduleRelaunchActivity(entry.getKey()); + } + } + } + + private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { + 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); + } + handleConfigurationChanged(mConfiguration, data.info); + WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); + } + + 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(); + } + 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(IBinder token, List<ResultInfo> results, String reason) { + ActivityClientRecord r = mActivities.get(token); + if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r); + if (r != null) { + 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. */ + ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, + int configChanges, boolean getNonConfigInstance, String reason) { + ActivityClientRecord r = mActivities.get(token); + Class<? extends Activity> activityClass = null; + if (localLOGV) Slog.v(TAG, "Performing finish of " + r); + if (r != null) { + 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(); + // updatePendingActivityConfiguration() reads from mActivities to update + // ActivityClientRecord which runs in a different thread. Protect modifications to + // mActivities to avoid race. + synchronized (mResourcesManager) { + mActivities.remove(token); + } + StrictMode.decrementExpectedActivityCount(activityClass); + return r; + } + + 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(IBinder token, boolean finishing, int configChanges, + boolean getNonConfigInstance, String reason) { + ActivityClientRecord r = performDestroyActivity(token, finishing, + configChanges, getNonConfigInstance, reason); + if (r != null) { + 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 { + 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(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(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) { + try { + ActivityTaskManager.getService().activityDestroyed(token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + 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; + + Configuration changedConfig = null; + 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)); + + if (mPendingConfiguration != null) { + changedConfig = mPendingConfiguration; + 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. + if (mConfiguration == null + || (tmp.createdConfig.isOtherSeqNewer(mConfiguration) + && mConfiguration.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) { + mCurDefaultDisplayDpi = changedConfig.densityDpi; + updateDefaultDensity(); + handleConfigurationChanged(changedConfig, null); + } + + 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; + + // If we are preserving the main window across relaunches we would also like to preserve + // the children. However the client side view system does not support preserving + // the child views so we notify the window manager to expect these windows to + // be replaced and defer requests to destroy or hide them. This way we can achieve + // visual continuity. It's important that we do this here prior to pause and destroy + // as that is when we may hide or remove the child views. + // + // There is another scenario, if we have decided locally to relaunch the app from a + // call to recreate, then none of the windows will be prepared for replacement or + // preserved by the server, so we want to notify it that we are preparing to replace + // everything + try { + if (r.mPreserveWindow) { + WindowManagerGlobal.getWindowSession().prepareToReplaceWindows( + r.token, true /* childrenOnly */); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents, + pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity"); + + if (pendingActions != null) { + // Only report a successful relaunch to WindowManager. + pendingActions.setReportRelaunchToWindowManager(true); + } + } + + /** + * 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. + */ + void scheduleRelaunchActivity(IBinder token) { + mH.removeMessages(H.RELAUNCH_ACTIVITY, token); + sendMessage(H.RELAUNCH_ACTIVITY, token); + } + + /** Performs the activity relaunch locally vs. requesting from system-server. */ + private 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_RESUME || prevState > ON_STOP) { + Log.w(TAG, "Activity state must be in [ON_RESUME..ON_STOP] in order to be relaunched," + + "current state is " + prevState); + return; + } + + + // Initialize a relaunch request. + final MergedConfiguration mergedConfiguration = new MergedConfiguration( + r.createdConfig != null ? r.createdConfig : mConfiguration, + 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.token, false, configChanges, true, reason); + + r.activity = null; + r.window = null; + r.hideForNow = false; + r.nextIdle = null; + // 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, customIntent); + } + + @Override + public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) { + try { + ActivityTaskManager.getService().activityRelaunched(token); + final ActivityClientRecord r = mActivities.get(token); + if (pendingActions.shouldReportRelaunchToWindowManager() && r != null + && r.window != null) { + r.window.reportActivityRelaunched(); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + 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); + } + } + + ArrayList<ComponentCallbacks2> collectComponentCallbacks( + boolean allActivities, Configuration newConfig) { + 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)); + } + final int NACT = mActivities.size(); + for (int i=0; i<NACT; i++) { + ActivityClientRecord ar = mActivities.valueAt(i); + Activity a = ar.activity; + if (a != null) { + Configuration thisConfig = applyConfigCompatMainThread( + mCurDefaultDisplayDpi, newConfig, + ar.packageInfo.getCompatibilityInfo()); + if (!ar.activity.mFinished && (allActivities || !ar.paused)) { + // If the activity is currently resumed, its configuration + // needs to change right now. + callbacks.add(a); + } else if (thisConfig != null) { + // Otherwise, we will tell it about the change + // the next time it is resumed or shown. Note that + // the activity manager may, before then, decide the + // activity needs to be destroyed to handle its new + // configuration. + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Setting activity " + + ar.activityInfo.name + " newConfig=" + thisConfig); + } + ar.newConfig = thisConfig; + } + } + } + final int NSVC = mServices.size(); + for (int i=0; i<NSVC; i++) { + callbacks.add(mServices.valueAt(i)); + } + } + 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}. + */ + private void performConfigurationChangedForActivity(ActivityClientRecord r, + Configuration newBaseConfig) { + performConfigurationChangedForActivity(r, newBaseConfig, + r.activity.getDisplayId(), false /* movedToDifferentDisplay */); + } + + /** + * 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. + * @param movedToDifferentDisplay Indicates if the activity was moved to different display. + * @return {@link Configuration} instance sent to client, null if not sent. + */ + private Configuration performConfigurationChangedForActivity(ActivityClientRecord r, + Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) { + r.tmpConfig.setTo(newBaseConfig); + if (r.overrideConfig != null) { + r.tmpConfig.updateFrom(r.overrideConfig); + } + final Configuration reportedConfig = performActivityConfigurationChanged(r.activity, + r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay); + freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig)); + return reportedConfig; + } + + /** + * Creates a new Configuration only if override would modify base. Otherwise returns base. + * @param base The base configuration. + * @param override The update to apply to the base configuration. Can be null. + * @return A Configuration representing base with override applied. + */ + private static Configuration createNewConfigAndUpdateIfNotNull(@NonNull Configuration base, + @Nullable Configuration override) { + if (override == null) { + return base; + } + Configuration newConfig = new Configuration(base); + newConfig.updateFrom(override); + return newConfig; + } + + /** + * Decides whether to update a component's configuration and whether to inform it. + * @param cb The component callback to notify of configuration change. + * @param newConfig The new configuration. + */ + private void performConfigurationChanged(ComponentCallbacks2 cb, Configuration newConfig) { + if (!REPORT_TO_ACTIVITY) { + return; + } + + // ContextThemeWrappers may override the configuration for that context. We must check and + // apply any overrides defined. + Configuration contextThemeWrapperOverrideConfig = null; + if (cb instanceof ContextThemeWrapper) { + final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb; + contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration(); + } + + // 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); + cb.onConfigurationChanged(configToReport); + } + + /** + * Decides whether to update an Activity's configuration and whether to inform it. + * @param activity The activity 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. + * @param movedToDifferentDisplay Indicates if the activity was moved to different display. + * @return Configuration sent to client, null if no changes and not moved to different display. + */ + private Configuration performActivityConfigurationChanged(Activity activity, + Configuration newConfig, Configuration amOverrideConfig, int displayId, + boolean movedToDifferentDisplay) { + if (activity == null) { + throw new IllegalArgumentException("No activity provided."); + } + final IBinder activityToken = activity.getActivityToken(); + if (activityToken == null) { + throw new IllegalArgumentException("Activity token not set. Is the activity attached?"); + } + + boolean shouldChangeConfig = false; + if (activity.mCurrentConfig == null) { + shouldChangeConfig = true; + } else { + // If the new config is the same as the config this Activity is already running with and + // the override config also didn't change, then don't bother calling + // onConfigurationChanged. + final int diff = activity.mCurrentConfig.diffPublicOnly(newConfig); + + if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken, + amOverrideConfig)) { + // Always send the task-level config changes. For system-level configuration, if + // this activity doesn't handle any of the config changes, then don't bother + // calling onConfigurationChanged as we're going to destroy it. + if (!mUpdatingSystemConfig + || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0 + || !REPORT_TO_ACTIVITY) { + shouldChangeConfig = true; + } + } + } + if (!shouldChangeConfig && !movedToDifferentDisplay) { + // Nothing significant, don't proceed with updating and reporting. + 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, movedToDifferentDisplay); + + activity.mConfigChangeFlags = 0; + activity.mCurrentConfig = new Configuration(newConfig); + + // 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 (!REPORT_TO_ACTIVITY) { + // Not configured to report to activity. + return configToReport; + } + + if (movedToDifferentDisplay) { + activity.dispatchMovedToDisplay(displayId, configToReport); + } + + if (shouldChangeConfig) { + activity.mCalled = false; + activity.onConfigurationChanged(configToReport); + if (!activity.mCalled) { + throw new SuperNotCalledException("Activity " + activity.getLocalClassName() + + " did not call through to super.onConfigurationChanged()"); + } + } + + return configToReport; + } + + public final void applyConfigurationToResources(Configuration config) { + synchronized (mResourcesManager) { + mResourcesManager.applyConfigurationToResourcesLocked(config, null); + } + } + + final Configuration applyCompatConfiguration(int displayDensity) { + Configuration config = mConfiguration; + if (mCompatConfiguration == null) { + mCompatConfiguration = new Configuration(); + } + mCompatConfiguration.setTo(mConfiguration); + if (mResourcesManager.applyCompatConfigurationLocked(displayDensity, + mCompatConfiguration)) { + config = mCompatConfiguration; + } + return config; + } + + @Override + public void handleConfigurationChanged(Configuration config) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); + mCurDefaultDisplayDpi = config.densityDpi; + mUpdatingSystemConfig = true; + try { + handleConfigurationChanged(config, null /* compat */); + } finally { + mUpdatingSystemConfig = false; + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { + + int configDiff; + boolean equivalent; + + final Theme systemTheme = getSystemContext().getTheme(); + final Theme systemUiTheme = getSystemUiContext().getTheme(); + + synchronized (mResourcesManager) { + if (mPendingConfiguration != null) { + if (!mPendingConfiguration.isOtherSeqNewer(config)) { + config = mPendingConfiguration; + mCurDefaultDisplayDpi = config.densityDpi; + updateDefaultDensity(); + } + mPendingConfiguration = null; + } + + if (config == null) { + return; + } + + // This flag tracks whether the new configuration is fundamentally equivalent to the + // existing configuration. This is necessary to determine whether non-activity callbacks + // should receive notice when the only changes are related to non-public fields. + // We do not gate calling {@link #performActivityConfigurationChanged} based on this + // flag as that method uses the same check on the activity config override as well. + equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); + + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + + config); + + mResourcesManager.applyConfigurationToResourcesLocked(config, compat); + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); + + if (mConfiguration == null) { + mConfiguration = new Configuration(); + } + if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { + return; + } + + configDiff = mConfiguration.updateFrom(config); + config = applyCompatConfiguration(mCurDefaultDisplayDpi); + + if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { + systemTheme.rebase(); + } + + if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { + systemUiTheme.rebase(); + } + } + + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config); + + freeTextLayoutCachesIfNeeded(configDiff); + + if (callbacks != null) { + final int N = callbacks.size(); + for (int i=0; i<N; i++) { + ComponentCallbacks2 cb = callbacks.get(i); + if (cb instanceof Activity) { + // If callback is an Activity - call corresponding method to consider override + // config and avoid onConfigurationChanged if it hasn't changed. + Activity a = (Activity) cb; + performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()), + config); + } else if (!equivalent) { + performConfigurationChanged(cb, config); + } + } + } + } + + /** + * 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; + } + + final String[] oldResDirs = new String[2]; + + if (apk != null) { + oldResDirs[0] = apk.getResDir(); + final ArrayList<String> oldPaths = new ArrayList<>(); + LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths); + apk.updateApplicationInfo(ai, oldPaths); + } + if (resApk != null) { + oldResDirs[1] = resApk.getResDir(); + 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.applyNewResourceDirsLocked(ai, oldResDirs); + } + + ApplicationPackageManager.configurationChanged(); + + // Trigger a regular Configuration change event, only with a different assetsSeq number + // so that we actually call through to all components. + // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to + // store configurations per-process. + Configuration newConfig = new Configuration(); + newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1; + handleConfigurationChanged(newConfig, null); + + // Preserve windows to avoid black flickers when overlays change. + relaunchAllActivities(true /* preserveWindows */); + } + + static void freeTextLayoutCachesIfNeeded(int configDiff) { + if (configDiff != 0) { + // Ask text layout engine to free its caches if there is a locale change + boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); + if (hasLocaleConfigChange) { + Canvas.freeTextLayoutCaches(); + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches"); + } + } + } + + @Override + public void updatePendingActivityConfiguration(IBinder activityToken, + Configuration overrideConfig) { + final ActivityClientRecord r; + synchronized (mResourcesManager) { + r = mActivities.get(activityToken); + } + + if (r == null) { + if (DEBUG_CONFIGURATION) { + Slog.w(TAG, "Not found target activity to update its pending config."); + } + return; + } + + synchronized (r) { + r.mPendingOverrideConfig = overrideConfig; + } + } + + /** + * Handle new activity configuration and/or move to a different display. + * @param activityToken Target activity token. + * @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. + */ + @Override + public void handleActivityConfigurationChanged(IBinder activityToken, + Configuration overrideConfig, int displayId) { + ActivityClientRecord r = mActivities.get(activityToken); + // Check input params. + if (r == null || r.activity == null) { + if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r); + return; + } + final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY + && displayId != r.activity.getDisplayId(); + + synchronized (r) { + if (r.mPendingOverrideConfig != null + && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { + overrideConfig = r.mPendingOverrideConfig; + } + r.mPendingOverrideConfig = null; + } + + 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 (movedToDifferentDisplay) { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:" + + r.activityInfo.name + ", displayId=" + displayId + + ", config=" + overrideConfig); + + final Configuration reportedConfig = performConfigurationChangedForActivity(r, + mCompatConfiguration, displayId, true /* movedToDifferentDisplay */); + if (viewRoot != null) { + viewRoot.onMovedToDisplay(displayId, reportedConfig); + } + } else { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: " + + r.activityInfo.name + ", config=" + overrideConfig); + performConfigurationChangedForActivity(r, mCompatConfiguration); + } + // 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) { + 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.applyNewResourceDirsLocked(aInfo, oldResDirs); + } + } catch (RemoteException e) { + } + } + } + } + + try { + getPackageManager().notifyPackagesReplacedReceived( + packagesHandled.toArray(new String[0])); + } catch (RemoteException ignored) { + } + + break; + } + } + ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); + } + + final void handleLowMemory() { + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); + + 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) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory"); + if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); + + ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); + + final int N = callbacks.size(); + for (int i = 0; i < N; i++) { + callbacks.get(i).onTrimMemory(level); + } + + WindowManagerGlobal.getInstance().trimMemory(level); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + 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 + System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath()); + } 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(); + if (codeCacheDir != null) { + try { + int uid = Process.myUid(); + String[] packages = getPackageManager().getPackagesForUid(uid); + if (packages != null) { + HardwareRenderer.setupDiskCache(codeCacheDir); + 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"); + } + } + + GraphicsEnvironment.getInstance().setup(context, mCoreSettings); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + private void updateDefaultDensity() { + final int densityDpi = mCurDefaultDisplayDpi; + if (!mDensityCompatMode + && densityDpi != Configuration.DENSITY_DPI_UNDEFINED + && densityDpi != DisplayMetrics.DENSITY_DEVICE) { + DisplayMetrics.DENSITY_DEVICE = densityDpi; + Bitmap.setDefaultDensity(densityDpi); + } + } + + /** + * 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; + } + + /** + * The LocaleList set for the app's resources may have been shuffled so that the preferred + * Locale is at position 0. We must find the index of this preferred Locale in the + * original LocaleList. + */ + private void updateLocaleListFromAppContext(Context context, LocaleList newLocaleList) { + final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); + final int newLocaleListSize = newLocaleList.size(); + for (int i = 0; i < newLocaleListSize; i++) { + if (bestLocale.equals(newLocaleList.get(i))) { + LocaleList.setDefault(newLocaleList, i); + return; + } + } + + // The app may have overridden the LocaleList with its own Locale + // (not present in the available list). Push the chosen Locale + // to the front of the list. + LocaleList.setDefault(new LocaleList(bestLocale, newLocaleList)); + } + + @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.enableRecentAllocations(true); + } + + // Note when this process has started. + Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); + + mBoundApplication = data; + mConfiguration = new Configuration(data.config); + mCompatConfiguration = new Configuration(data.config); + + 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; + 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, + 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); + + // Prior to P, internal calls to decode Bitmaps used BitmapFactory, + // which may scale up to account for density. In P, we switched to + // ImageDecoder, which skips the upscale to save memory. ImageDecoder + // needs to still scale up in older apps, in case they rely on the + // size of the Bitmap without considering its density. + ImageDecoder.sApiLevel = 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()); + + 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.applyConfigurationToResourcesLocked(data.config, data.compatInfo); + mCurDefaultDisplayDpi = data.config.densityDpi; + + // This calls mResourcesManager so keep it within the synchronized block. + applyCompatConfiguration(mCurDefaultDisplayDpi); + } + + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + + 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); + } + updateDefaultDensity(); + + 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); + + if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) { + // XXX should have option to change the port. + Debug.changeDebugPort(8100); + if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) { + Slog.w(TAG, "Application " + data.info.getPackageName() + + " is waiting for the debugger on port 8100..."); + + IActivityManager mgr = ActivityManager.getService(); + try { + mgr.showWaitingForDebugger(mAppThread, true); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + + Debug.waitForDebugger(); + + try { + mgr.showWaitingForDebugger(mAppThread, false); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + + } else { + Slog.w(TAG, "Application " + data.info.getPackageName() + + " can be debugged on port 8100..."); + } + } + + // Allow binder tracing, and application-generated systrace messages if we're profileable. + boolean isAppProfileable = data.appInfo.isProfileableByShell(); + Trace.setAppTracingAllowed(isAppProfileable); + if (isAppProfileable && data.enableBinderTracking) { + Binder.enableTracing(); + } + + // Initialize heap profiling. + if (isAppProfileable || Build.IS_DEBUGGABLE) { + nInitZygoteChildHeapProfiling(); + } + + // Allow renderer debugging features if we're debuggable. + boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE); + HardwareRenderer.setPackageName(data.appInfo.packageName); + + /** + * Initialize the default http proxy in this process for the reasons we set the time zone. + */ + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies"); + final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + if (b != null) { + // In pre-boot mode (doing initial launch to collect password), not + // all system is up. This includes the connectivity service, so don't + // crash if we can't get it. + final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + try { + Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null)); + } catch (RemoteException e) { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + throw e.rethrowFromSystemServer(); + } + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + // Instrumentation info affects the class loader, so load it before + // setting up the app context. + final InstrumentationInfo ii; + if (data.instrumentationName != null) { + try { + ii = new ApplicationPackageManager(null, getPackageManager()) + .getInstrumentationInfo(data.instrumentationName, 0); + } catch (PackageManager.NameNotFoundException e) { + 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(); + } else { + ii = null; + } + + final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); + updateLocaleListFromAppContext(appContext, + mResourcesManager.getConfiguration().getLocales()); + + 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); + + // Continue loading instrumentation. + if (ii != null) { + 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); + } + } 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(); + 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.makeApplication(data.restrictedBackupMode, null); + + // Propagate autofill compat state + app.setAutofillOptions(data.autofillOptions); + + // Propagate Content Capture options + app.setContentCaptureOptions(data.contentCaptureOptions); + + mInitialApplication = 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(); + } + } + } + + /*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(); + } + } + + @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; + try { + synchronized (getGetProviderLock(auth, userId)) { + holder = ActivityManager.getService().getContentProvider( + getApplicationThread(), c.getOpPackageName(), auth, userId, stable); + } + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + if (holder == null) { + Slog.e(TAG, "Failed to find provider info for " + auth); + 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 Object getGetProviderLock(String auth, int userId) { + final ProviderKey key = new ProviderKey(auth, userId); + synchronized (mGetProviderLocks) { + Object lock = mGetProviderLocks.get(key); + if (lock == null) { + lock = key; + mGetProviderLocks.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); + } + } + + 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; + mSystemThread = system; + 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.makeApplication(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.applyConfigurationToResourcesLocked(globalConfig, + null /* compat */)) { + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); + + // This actually changed the resources! Tell everyone about it. + if (mPendingConfiguration == null + || mPendingConfiguration.isOtherSeqNewer(globalConfig)) { + mPendingConfiguration = globalConfig; + sendMessage(H.CONFIGURATION_CHANGED, globalConfig); + } + } + } + }; + ViewRootImpl.addConfigCallback(configChangedCallback); + } + + @UnsupportedAppUsage + public static ActivityThread systemMain() { + // The system process on low-memory devices do not get to use hardware + // accelerated drawing, since this can add too much overhead to the + // process. + if (!ActivityManager.isHighEndGfx()) { + ThreadedRenderer.disable(true); + } else { + ThreadedRenderer.enableForegroundTrimming(); + } + ActivityThread thread = new ActivityThread(); + thread.attach(true, 0); + return thread; + } + + public static void updateHttpProxy(@NonNull Context context) { + final ConnectivityManager cm = ConnectivityManager.from(context); + Proxy.setHttpProxySystemProperty(cm.getDefaultProxy()); + } + + @UnsupportedAppUsage + public final void installSystemProviders(List<ProviderInfo> providers) { + if (providers != null) { + installContentProviders(mInitialApplication, providers); + } + } + + public int getIntCoreSetting(String key, int defaultValue) { + synchronized (mResourcesManager) { + if (mCoreSettings != null) { + return mCoreSettings.getInt(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) { + if (e.errno == OsConstants.EXDEV) { + Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath); + try { + Files.move(new File(oldPath).toPath(), new File(newPath).toPath()); + } catch (IOException 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); + + 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"); + } + + private void purgePendingResources() { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources"); + nPurgePendingResources(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + // ------------------ Regular JNI ------------------------ + private native void nPurgePendingResources(); + private native void nDumpGraphicsInfo(FileDescriptor fd); + private native void nInitZygoteChildHeapProfiling(); +}
diff --git a/android/app/ActivityTransitionCoordinator.java b/android/app/ActivityTransitionCoordinator.java new file mode 100644 index 0000000..4b87a64 --- /dev/null +++ b/android/app/ActivityTransitionCoordinator.java
@@ -0,0 +1,1121 @@ +/* + * 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); + 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/app/ActivityTransitionState.java b/android/app/ActivityTransitionState.java new file mode 100644 index 0000000..3a95839 --- /dev/null +++ b/android/app/ActivityTransitionState.java
@@ -0,0 +1,393 @@ +/* + * 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.get() == 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) { + 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(); + if (mEnterActivityOptions.isReturning()) { + restoreExitedViews(); + activity.getWindow().getDecorView().setVisibility(View.VISIBLE); + } + 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() { + restoreExitedViews(); + if (mEnterTransitionCoordinator != null) { + mEnterTransitionCoordinator.stop(); + mEnterTransitionCoordinator = null; + } + if (mReturnExitCoordinator != null) { + mReturnExitCoordinator.stop(); + 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(); + } + } + }, 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(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.mResultCode, + activity.mResultData); + } + }); + } else { + mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData); + } + } + 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) { + 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/app/ActivityView.java b/android/app/ActivityView.java new file mode 100644 index 0000000..4771f9f --- /dev/null +++ b/android/app/ActivityView.java
@@ -0,0 +1,718 @@ +/** + * 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.app; + +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; +import static android.view.Display.INVALID_DISPLAY; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.app.ActivityManager.StackInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Insets; +import android.graphics.Matrix; +import android.graphics.Region; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.hardware.input.InputManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.IWindowManager; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceSession; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.inputmethod.InputMethodManager; + +import dalvik.system.CloseGuard; + +import java.util.List; + +/** + * Activity container that allows launching activities into itself. + * <p>Activity launching into this container is restricted by the same rules that apply to launching + * on VirtualDisplays. + * @hide + */ +@TestApi +public class ActivityView extends ViewGroup { + + private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay"; + private static final String TAG = "ActivityView"; + + private VirtualDisplay mVirtualDisplay; + private final SurfaceView mSurfaceView; + + /** + * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be + * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl. + */ + private SurfaceControl mRootSurfaceControl; + + private final SurfaceCallback mSurfaceCallback; + private StateCallback mActivityViewCallback; + + private IActivityTaskManager mActivityTaskManager; + // Temp container to store view coordinates in window. + private final int[] mLocationInWindow = new int[2]; + + // The latest tap exclude region that we've sent to WM. + private final Region mTapExcludeRegion = new Region(); + + private TaskStackListener mTaskStackListener; + + private final CloseGuard mGuard = CloseGuard.get(); + private boolean mOpened; // Protected by mGuard. + + private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); + + /** The ActivityView is only allowed to contain one task. */ + private final boolean mSingleTaskInstance; + + private Insets mForwardedInsets; + + public ActivityView(Context context) { + this(context, null /* attrs */); + } + + public ActivityView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyle */); + } + + public ActivityView(Context context, AttributeSet attrs, int defStyle) { + this(context, attrs, defStyle, false /*singleTaskInstance*/); + } + + public ActivityView( + Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) { + super(context, attrs, defStyle); + mSingleTaskInstance = singleTaskInstance; + + mActivityTaskManager = ActivityTaskManager.getService(); + mSurfaceView = new SurfaceView(context); + mSurfaceCallback = new SurfaceCallback(); + mSurfaceView.getHolder().addCallback(mSurfaceCallback); + addView(mSurfaceView); + + mOpened = true; + mGuard.open("release"); + } + + /** Callback that notifies when the container is ready or destroyed. */ + public abstract static class StateCallback { + + /** + * Called when the container is ready for launching activities. Calling + * {@link #startActivity(Intent)} prior to this callback will result in an + * {@link IllegalStateException}. + * + * @see #startActivity(Intent) + */ + public abstract void onActivityViewReady(ActivityView view); + + /** + * Called when the container can no longer launch activities. Calling + * {@link #startActivity(Intent)} after this callback will result in an + * {@link IllegalStateException}. + * + * @see #startActivity(Intent) + */ + public abstract void onActivityViewDestroyed(ActivityView view); + + /** + * Called when a task is created inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskCreated(int taskId, ComponentName componentName) { } + + /** + * Called when a task is moved to the front of the stack inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskMovedToFront(int taskId) { } + + /** + * Called when a task is about to be removed from the stack inside the container. + * This is a filtered version of {@link TaskStackListener} + */ + public void onTaskRemovalStarted(int taskId) { } + } + + /** + * Set the callback to be notified about state changes. + * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called. + * <p>Note: If the instance was ready prior to this call being made, then + * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within + * this method call. + * + * @param callback The callback to report events to. + * + * @see StateCallback + * @see #startActivity(Intent) + */ + public void setCallback(StateCallback callback) { + mActivityViewCallback = callback; + + if (mVirtualDisplay != null && mActivityViewCallback != null) { + mActivityViewCallback.onActivityViewReady(this); + } + } + + /** + * Sets the corner radius for the Activity displayed here. The corners will be + * cropped from the window painted by the contained Activity. + * + * @param cornerRadius the radius for the corners, in pixels + * @hide + */ + public void setCornerRadius(float cornerRadius) { + mSurfaceView.setCornerRadius(cornerRadius); + } + + /** + * Launch a new activity into this container. + * <p>Activity resolved by the provided {@link Intent} must have + * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be + * launched here. Also, if activity is not owned by the owner of this container, it must allow + * embedding and the caller must have permission to embed. + * <p>Note: This class must finish initializing and + * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before + * this method can be called. + * + * @param intent Intent used to launch an activity. + * + * @see StateCallback + * @see #startActivity(PendingIntent) + */ + public void startActivity(@NonNull Intent intent) { + final ActivityOptions options = prepareActivityOptions(); + getContext().startActivity(intent, options.toBundle()); + } + + /** + * Launch a new activity into this container. + * <p>Activity resolved by the provided {@link Intent} must have + * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be + * launched here. Also, if activity is not owned by the owner of this container, it must allow + * embedding and the caller must have permission to embed. + * <p>Note: This class must finish initializing and + * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before + * this method can be called. + * + * @param intent Intent used to launch an activity. + * @param user The UserHandle of the user to start this activity for. + * + * + * @see StateCallback + * @see #startActivity(PendingIntent) + */ + public void startActivity(@NonNull Intent intent, UserHandle user) { + final ActivityOptions options = prepareActivityOptions(); + getContext().startActivityAsUser(intent, options.toBundle(), user); + } + + /** + * Launch a new activity into this container. + * <p>Activity resolved by the provided {@link PendingIntent} must have + * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be + * launched here. Also, if activity is not owned by the owner of this container, it must allow + * embedding and the caller must have permission to embed. + * <p>Note: This class must finish initializing and + * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before + * this method can be called. + * + * @param pendingIntent Intent used to launch an activity. + * + * @see StateCallback + * @see #startActivity(Intent) + */ + public void startActivity(@NonNull PendingIntent pendingIntent) { + final ActivityOptions options = prepareActivityOptions(); + try { + pendingIntent.send(null /* context */, 0 /* code */, null /* intent */, + null /* onFinished */, null /* handler */, null /* requiredPermission */, + options.toBundle()); + } catch (PendingIntent.CanceledException e) { + throw new RuntimeException(e); + } + } + + /** + * Launch a new activity into this container. + * <p>Activity resolved by the provided {@link PendingIntent} must have + * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be + * launched here. Also, if activity is not owned by the owner of this container, it must allow + * embedding and the caller must have permission to embed. + * <p>Note: This class must finish initializing and + * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before + * this method can be called. + * + * @param pendingIntent Intent used to launch an activity. + * @param options options for the activity + * + * @see StateCallback + * @see #startActivity(Intent) + */ + public void startActivity(@NonNull PendingIntent pendingIntent, + @NonNull ActivityOptions options) { + options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); + try { + pendingIntent.send(null /* context */, 0 /* code */, null /* intent */, + null /* onFinished */, null /* handler */, null /* requiredPermission */, + options.toBundle()); + } catch (PendingIntent.CanceledException e) { + throw new RuntimeException(e); + } + } + + /** + * Check if container is ready to launch and create {@link ActivityOptions} to target the + * virtual display. + */ + private ActivityOptions prepareActivityOptions() { + if (mVirtualDisplay == null) { + throw new IllegalStateException( + "Trying to start activity before ActivityView is ready."); + } + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); + return options; + } + + /** + * Release this container. Activity launching will no longer be permitted. + * <p>Note: Calling this method is allowed after + * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before + * {@link StateCallback#onActivityViewDestroyed(ActivityView)}. + * + * @see StateCallback + */ + public void release() { + if (mVirtualDisplay == null) { + throw new IllegalStateException( + "Trying to release container that is not initialized."); + } + performRelease(); + } + + /** + * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude + * regions and avoid focus switches by touches on this view. + */ + public void onLocationChanged() { + updateLocationAndTapExcludeRegion(); + } + + private void clearActivityViewGeometryForIme() { + if (mVirtualDisplay == null) { + return; + } + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null); + } + + @Override + public void onLayout(boolean changed, int l, int t, int r, int b) { + mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */); + } + + @Override + public boolean gatherTransparentRegion(Region region) { + // The tap exclude region may be affected by any view on top of it, so we detect the + // possible change by monitoring this function. + updateLocationAndTapExcludeRegion(); + return super.gatherTransparentRegion(region); + } + + /** + * Sends current location in window and tap exclude region to WM for this view. + */ + private void updateLocationAndTapExcludeRegion() { + if (mVirtualDisplay == null || !isAttachedToWindow()) { + return; + } + try { + int x = mLocationInWindow[0]; + int y = mLocationInWindow[1]; + getLocationInWindow(mLocationInWindow); + if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) { + x = mLocationInWindow[0]; + y = mLocationInWindow[1]; + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + WindowManagerGlobal.getWindowSession().updateDisplayContentLocation( + getWindow(), x, y, displayId); + + // Also report this geometry information to InputMethodManagerService. + // TODO(b/115693908): Unify this logic into the above WMS-based one. + final Matrix matrix = new Matrix(); + matrix.set(getMatrix()); + matrix.postTranslate(x, y); + mContext.getSystemService(InputMethodManager.class) + .reportActivityView(displayId, matrix); + } + updateTapExcludeRegion(x, y); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** Computes and sends current tap exclude region to WM for this view. */ + private void updateTapExcludeRegion(int x, int y) throws RemoteException { + if (!canReceivePointerEvents()) { + cleanTapExcludeRegion(); + return; + } + mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight()); + + // There might be views on top of us. We need to subtract those areas from the tap + // exclude region. + final ViewParent parent = getParent(); + if (parent != null) { + parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this); + } + + WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), + mTapExcludeRegion); + } + + private class SurfaceCallback implements SurfaceHolder.Callback { + @Override + public void surfaceCreated(SurfaceHolder surfaceHolder) { + if (mVirtualDisplay == null) { + initVirtualDisplay(new SurfaceSession()); + if (mVirtualDisplay != null && mActivityViewCallback != null) { + mActivityViewCallback.onActivityViewReady(ActivityView.this); + } + } else { + mTmpTransaction.reparent(mRootSurfaceControl, + mSurfaceView.getSurfaceControl()).apply(); + } + + if (mVirtualDisplay != null) { + mVirtualDisplay.setDisplayState(true); + } + + updateLocationAndTapExcludeRegion(); + } + + @Override + public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { + if (mVirtualDisplay != null) { + mVirtualDisplay.resize(width, height, getBaseDisplayDensity()); + } + updateLocationAndTapExcludeRegion(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder surfaceHolder) { + if (mVirtualDisplay != null) { + mVirtualDisplay.setDisplayState(false); + } + clearActivityViewGeometryForIme(); + cleanTapExcludeRegion(); + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + mSurfaceView.setVisibility(visibility); + } + + /** + * @return the display id of the virtual display. + */ + public int getVirtualDisplayId() { + if (mVirtualDisplay != null) { + return mVirtualDisplay.getDisplay().getDisplayId(); + } + return INVALID_DISPLAY; + } + + /** + * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the + * virtual display. + */ + public void performBackPress() { + if (mVirtualDisplay == null) { + return; + } + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + final InputManager im = InputManager.getInstance(); + im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId), + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId), + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + private static KeyEvent createKeyEvent(int action, int code, int displayId) { + long when = SystemClock.uptimeMillis(); + final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, + 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + ev.setDisplayId(displayId); + return ev; + } + + private void initVirtualDisplay(SurfaceSession surfaceSession) { + if (mVirtualDisplay != null) { + throw new IllegalStateException("Trying to initialize for the second time."); + } + + final int width = mSurfaceView.getWidth(); + final int height = mSurfaceView.getHeight(); + final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + + mVirtualDisplay = displayManager.createVirtualDisplay( + DISPLAY_NAME + "@" + System.identityHashCode(this), width, height, + getBaseDisplayDensity(), null, + VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL); + if (mVirtualDisplay == null) { + Log.e(TAG, "Failed to initialize ActivityView"); + return; + } + + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + + mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession) + .setContainerLayer() + .setParent(mSurfaceView.getSurfaceControl()) + .setName(DISPLAY_NAME) + .build(); + + try { + // TODO: Find a way to consolidate these calls to the server. + WindowManagerGlobal.getWindowSession().reparentDisplayContent( + getWindow(), mRootSurfaceControl, displayId); + wm.dontOverrideDisplayInfo(displayId); + if (mSingleTaskInstance) { + mActivityTaskManager.setDisplayToSingleTaskInstance(displayId); + } + wm.setForwardedInsets(displayId, mForwardedInsets); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + + mTmpTransaction.show(mRootSurfaceControl).apply(); + mTaskStackListener = new TaskStackListenerImpl(); + try { + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register task stack listener", e); + } + } + + private void performRelease() { + if (!mOpened) { + return; + } + + mSurfaceView.getHolder().removeCallback(mSurfaceCallback); + + cleanTapExcludeRegion(); + + if (mTaskStackListener != null) { + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister task stack listener", e); + } + mTaskStackListener = null; + } + + final boolean displayReleased; + if (mVirtualDisplay != null) { + mVirtualDisplay.release(); + mVirtualDisplay = null; + displayReleased = true; + } else { + displayReleased = false; + } + + if (displayReleased && mActivityViewCallback != null) { + mActivityViewCallback.onActivityViewDestroyed(this); + } + + mGuard.close(); + mOpened = false; + } + + /** Report to server that tap exclude region on hosting display should be cleared. */ + private void cleanTapExcludeRegion() { + if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) { + return; + } + // Update tap exclude region with a null region to clean the state on server. + try { + WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), + null /* region */); + mTapExcludeRegion.setEmpty(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** Get density of the hosting display. */ + private int getBaseDisplayDensity() { + final WindowManager wm = mContext.getSystemService(WindowManager.class); + final DisplayMetrics metrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metrics); + return metrics.densityDpi; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mGuard != null) { + mGuard.warnIfOpen(); + performRelease(); + } + } finally { + super.finalize(); + } + } + + /** + * Set forwarded insets on the virtual display. + * + * @see IWindowManager#setForwardedInsets + */ + public void setForwardedInsets(Insets insets) { + mForwardedInsets = insets; + if (mVirtualDisplay == null) { + return; + } + try { + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * A task change listener that detects background color change of the topmost stack on our + * virtual display and updates the background of the surface view. This background will be shown + * when surface view is resized, but the app hasn't drawn its content in new size yet. + * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack + * associated with the {@link ActivityView} has had a Task moved to the front. This is useful + * when needing to also bring the host Activity to the foreground at the same time. + */ + private class TaskStackListenerImpl extends TaskStackListener { + + @Override + public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) + throws RemoteException { + if (mVirtualDisplay == null + || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + if (stackInfo == null) { + return; + } + // Found the topmost stack on target display. Now check if the topmost task's + // description changed. + if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mSurfaceView.setResizeBackgroundColor( + taskInfo.taskDescription.getBackgroundColor()); + } + } + + @Override + public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) + throws RemoteException { + if (mActivityViewCallback == null || mVirtualDisplay == null + || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or unrelated to the "move to front" then there's no use + // notifying the callback + if (stackInfo != null + && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId); + } + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { + if (mActivityViewCallback == null || mVirtualDisplay == null) { + return; + } + + StackInfo stackInfo = getTopMostStackInfo(); + // if StackInfo was null or unrelated to the task creation then there's no use + // notifying the callback + if (stackInfo != null + && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { + mActivityViewCallback.onTaskCreated(taskId, componentName); + } + } + + @Override + public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo) + throws RemoteException { + if (mActivityViewCallback == null || mVirtualDisplay == null + || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) { + return; + } + mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId); + } + + private StackInfo getTopMostStackInfo() throws RemoteException { + // Find the topmost task on our virtual display - it will define the background + // color of the surface view during resizing. + final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); + final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos(); + + // Iterate through stacks from top to bottom. + final int stackCount = stackInfoList.size(); + for (int i = 0; i < stackCount; i++) { + final StackInfo stackInfo = stackInfoList.get(i); + // Only look for stacks on our virtual display. + if (stackInfo.displayId != displayId) { + continue; + } + // Found the topmost stack on target display. + return stackInfo; + } + return null; + } + } +}
diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java new file mode 100644 index 0000000..3a34b79 --- /dev/null +++ b/android/app/AlarmManager.java
@@ -0,0 +1,1160 @@ +/* + * 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.IntDef; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.WorkSource; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import libcore.timezone.ZoneInfoDB; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 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"; + + /** @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"; + + /** @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. + * + * @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; + + @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. + */ + public void onAlarm(); + } + + final class ListenerWrapper extends IAlarmListener.Stub implements Runnable { + final OnAlarmListener mListener; + Handler mHandler; + IAlarmCompleteListener mCompletion; + + public ListenerWrapper(OnAlarmListener listener) { + mListener = listener; + } + + public void setHandler(Handler h) { + mHandler = h; + } + + public void cancel() { + try { + mService.remove(null, this); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + + synchronized (AlarmManager.class) { + if (sWrappers != null) { + sWrappers.remove(mListener); + } + } + } + + @Override + public void doAlarm(IAlarmCompleteListener alarmManager) { + mCompletion = alarmManager; + + // Remove this listener from the wrapper cache first; the server side + // already considers it gone + synchronized (AlarmManager.class) { + if (sWrappers != null) { + sWrappers.remove(mListener); + } + } + + mHandler.post(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 -> wrapper mapping, for cancel() support. + // Access is synchronized on the AlarmManager class object. + private static ArrayMap<OnAlarmListener, 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, PendingIntent operation) { + setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null, + 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, String tag, OnAlarmListener listener, + 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. + * + * @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 + */ + public void setRepeating(@AlarmType int type, long triggerAtMillis, + long intervalMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, + null, null, 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> + * 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, + PendingIntent operation) { + setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, + null, null, 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. + */ + public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, + String tag, OnAlarmListener listener, Handler targetHandler) { + setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag, + targetHandler, 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. + * + * @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 + */ + public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, 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. + */ + public void setExact(@AlarmType int type, long triggerAtMillis, String tag, + OnAlarmListener listener, 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, String tag, + OnAlarmListener listener, 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}. + * + * @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 + */ + public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { + setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, + null, null, null, null, info); + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) + public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, + long intervalMillis, PendingIntent operation, WorkSource workSource) { + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null, + 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, String tag, OnAlarmListener listener, Handler targetHandler, + WorkSource workSource) { + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag, + targetHandler, 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 + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) + public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, + long intervalMillis, OnAlarmListener listener, Handler targetHandler, + WorkSource workSource) { + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null, + targetHandler, 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) { + 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 ArrayMap<OnAlarmListener, ListenerWrapper>(); + } + + recipientWrapper = sWrappers.get(listener); + // no existing wrapper => build a new one + if (recipientWrapper == null) { + recipientWrapper = new ListenerWrapper(listener); + sWrappers.put(listener, recipientWrapper); + } + } + + final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler; + recipientWrapper.setHandler(handler); + } + + 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. + * + * @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 + */ + public void setInexactRepeating(@AlarmType int type, long triggerAtMillis, + long intervalMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, + null, 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 whitelist 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, + PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, + operation, null, null, 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 whitelist 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> + * + * @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 + */ + public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, + PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, + null, null, null, null, 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(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(OnAlarmListener listener) { + if (listener == null) { + throw new NullPointerException("cancel() called with a null OnAlarmListener"); + } + + ListenerWrapper wrapper = null; + synchronized (AlarmManager.class) { + if (sWrappers != null) { + wrapper = sWrappers.get(listener); + } + } + + if (wrapper == null) { + Log.w(TAG, "Unrecognized alarm listener " + listener); + return; + } + + wrapper.cancel(); + } + + /** + * 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 = false; + try { + hasTimeZone = ZoneInfoDB.getInstance().hasTimeZone(timeZone); + } catch (IOException ignored) { + } + + 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(); + } + } + + /** + * 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 + */ + 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 writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime); + if (mShowIntent != null) { + mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT); + } + proto.end(token); + } + } +}
diff --git a/android/app/AlertDialog.java b/android/app/AlertDialog.java new file mode 100644 index 0000000..bfc216a --- /dev/null +++ b/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.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 positive 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 listener) { + P.mAdapter = adapter; + P.mOnClickListener = listener; + P.mCheckedItem = checkedItem; + P.mIsSingleChoice = true; + return this; + } + + /** + * Sets a listener to be invoked when an item in the list is selected. + * + * @param listener the listener to be invoked + * @return this Builder object to allow for chaining of calls to set methods + * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener) + */ + public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) { + P.mOnItemSelectedListener = listener; + return this; + } + + /** + * Set a custom view resource to be the contents of the Dialog. The + * resource will be inflated, adding all top-level views to the screen. + * + * @param layoutResId Resource ID to be inflated. + * @return this Builder object to allow for chaining of calls to set + * methods + */ + public Builder setView(int layoutResId) { + P.mView = null; + P.mViewLayoutResId = layoutResId; + P.mViewSpacingSpecified = false; + return this; + } + + /** + * Sets a custom view to be the contents of the alert dialog. + * <p> + * When using a pre-Holo theme, if the supplied view is an instance of + * a {@link ListView} then the light background will be used. + * <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 view the view to use as the contents of the alert dialog + * @return this Builder object to allow for chaining of calls to set + * methods + */ + public Builder setView(View view) { + P.mView = view; + P.mViewLayoutResId = 0; + P.mViewSpacingSpecified = false; + return this; + } + + /** + * Sets a custom view to be the contents of the alert dialog and + * specifies additional padding around that view. + * <p> + * When using a pre-Holo theme, if the supplied view is an instance of + * a {@link ListView} then the light background will be used. + * <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 view the view to use as the contents of the alert dialog + * @param viewSpacingLeft spacing between the left edge of the view and + * the dialog frame + * @param viewSpacingTop spacing between the top edge of the view and + * the dialog frame + * @param viewSpacingRight spacing between the right edge of the view + * and the dialog frame + * @param viewSpacingBottom spacing between the bottom edge of the view + * and the dialog frame + * @return this Builder object to allow for chaining of calls to set + * methods + * + * @hide Remove once the framework usages have been replaced. + * @deprecated Set the padding on the view itself. + */ + @Deprecated + @UnsupportedAppUsage + public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop, + int viewSpacingRight, int viewSpacingBottom) { + P.mView = view; + P.mViewLayoutResId = 0; + P.mViewSpacingSpecified = true; + P.mViewSpacingLeft = viewSpacingLeft; + P.mViewSpacingTop = viewSpacingTop; + P.mViewSpacingRight = viewSpacingRight; + P.mViewSpacingBottom = viewSpacingBottom; + return this; + } + + /** + * Sets the alert dialog to use the inverse background, regardless of + * what the contents is. + * + * @param useInverseBackground whether to use the inverse background + * @return this Builder object to allow for chaining of calls to set methods + * @deprecated This flag is only used for pre-Material themes. Instead, + * specify the window background using on the alert dialog + * theme. + */ + @Deprecated + public Builder setInverseBackgroundForced(boolean useInverseBackground) { + P.mForceInverseBackground = useInverseBackground; + return this; + } + + /** + * @hide + */ + @UnsupportedAppUsage + public Builder setRecycleOnMeasureEnabled(boolean enabled) { + P.mRecycleOnMeasure = enabled; + return this; + } + + + /** + * Creates an {@link AlertDialog} with the arguments supplied to this + * builder. + * <p> + * Calling this method does not display the dialog. If no additional + * processing is needed, {@link #show()} may be called instead to both + * create and display the dialog. + */ + public AlertDialog create() { + // Context has already been wrapped with the appropriate theme. + final AlertDialog dialog = new AlertDialog(P.mContext, 0, false); + P.apply(dialog.mAlert); + dialog.setCancelable(P.mCancelable); + if (P.mCancelable) { + dialog.setCanceledOnTouchOutside(true); + } + dialog.setOnCancelListener(P.mOnCancelListener); + dialog.setOnDismissListener(P.mOnDismissListener); + if (P.mOnKeyListener != null) { + dialog.setOnKeyListener(P.mOnKeyListener); + } + return dialog; + } + + /** + * Creates an {@link AlertDialog} with the arguments supplied to this + * builder and immediately displays the dialog. + * <p> + * Calling this method is functionally identical to: + * <pre> + * AlertDialog dialog = builder.create(); + * dialog.show(); + * </pre> + */ + public AlertDialog show() { + final AlertDialog dialog = create(); + dialog.show(); + return dialog; + } + } + +}
diff --git a/android/app/AliasActivity.java b/android/app/AliasActivity.java new file mode 100644 index 0000000..3756529 --- /dev/null +++ b/android/app/AliasActivity.java
@@ -0,0 +1,124 @@ +/* + * 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 org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Xml; + +import com.android.internal.util.XmlUtils; + +import java.io.IOException; + +/** + * Stub activity that launches another activity (and then finishes itself) + * based on information in its component's manifest meta-data. This is a + * simple way to implement an alias-like mechanism. + * + * To use this activity, you should include in the manifest for the associated + * component an entry named "android.app.alias". It is a reference to an XML + * resource describing an intent that launches the real application. + */ +public class AliasActivity extends Activity { + /** + * This is the name under which you should store in your component the + * meta-data information about the alias. It is a reference to an XML + * resource describing an intent that launches the real application. + * {@hide} + */ + public final String ALIAS_META_DATA = "android.app.alias"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + XmlResourceParser parser = null; + try { + ActivityInfo ai = getPackageManager().getActivityInfo( + getComponentName(), PackageManager.GET_META_DATA); + parser = ai.loadXmlMetaData(getPackageManager(), + ALIAS_META_DATA); + if (parser == null) { + throw new RuntimeException("Alias requires a meta-data field " + + ALIAS_META_DATA); + } + + Intent intent = parseAlias(parser); + if (intent == null) { + throw new RuntimeException( + "No <intent> tag found in alias description"); + } + + startActivity(intent); + finish(); + + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Error parsing alias", e); + } catch (XmlPullParserException e) { + throw new RuntimeException("Error parsing alias", e); + } catch (IOException e) { + throw new RuntimeException("Error parsing alias", e); + } finally { + if (parser != null) parser.close(); + } + } + + private Intent parseAlias(XmlPullParser parser) + throws XmlPullParserException, IOException { + AttributeSet attrs = Xml.asAttributeSet(parser); + + Intent intent = null; + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"alias".equals(nodeName)) { + throw new RuntimeException( + "Alias meta-data must start with <alias> tag; found" + + nodeName + " at " + parser.getPositionDescription()); + } + + int outerDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + nodeName = parser.getName(); + if ("intent".equals(nodeName)) { + Intent gotIntent = Intent.parseIntent(getResources(), parser, attrs); + if (intent == null) intent = gotIntent; + } else { + XmlUtils.skipCurrentTag(parser); + } + } + + return intent; + } + +}
diff --git a/android/app/AppComponentFactory.java b/android/app/AppComponentFactory.java new file mode 100644 index 0000000..5b02817 --- /dev/null +++ b/android/app/AppComponentFactory.java
@@ -0,0 +1,154 @@ +/* + * 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.ContentProvider; +import android.content.Intent; +import android.content.pm.ApplicationInfo; + +/** + * Interface used to control the instantiation of manifest elements. + * + * @see #instantiateApplication + * @see #instantiateActivity + * @see #instantiateClassLoader + * @see #instantiateService + * @see #instantiateReceiver + * @see #instantiateProvider + */ +public class AppComponentFactory { + + /** + * Selects the class loader which will be used by the platform to instantiate app components. + * <p> + * The default implementation of this method returns the {@code cl} parameter unchanged. + * Applications can override this method to set up a custom class loader or a custom class + * loader hierarchy and return it to the platform. + * <p> + * The method is a hook invoked before any application components are instantiated or the + * application Context is initialized. It is intended to allow the application's classes to + * be loaded from a different source than the base/split APK(s). + * <p> + * The default class loader {@code cl} is created by the platform and used to load the + * application's base or split APK(s). Its parent is typically the boot class loader, unless + * running under instrumentation. Its classname is configurable using the + * {@link android.R.attr#classLoader} manifest attribute. + * + * @param cl The default class loader created by the platform. + * @param aInfo Information about the application being loaded. + */ + public @NonNull ClassLoader instantiateClassLoader(@NonNull ClassLoader cl, + @NonNull ApplicationInfo aInfo) { + return cl; + } + + /** + * Allows application to override the creation of the application object. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Application object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + */ + public @NonNull Application instantiateApplication(@NonNull ClassLoader cl, + @NonNull String className) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Application) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of activities. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Activity object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + */ + public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, + @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Activity) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of receivers. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + */ + public @NonNull BroadcastReceiver instantiateReceiver(@NonNull ClassLoader cl, + @NonNull String className, @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (BroadcastReceiver) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of services. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the Service object. The returned object will not be initialized + * as a Context yet and should not be used to interact with other android APIs. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + */ + public @NonNull Service instantiateService(@NonNull ClassLoader cl, + @NonNull String className, @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Service) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of providers. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * <p> + * This method is only intended to provide a hook for instantiation. It does not provide + * earlier access to the ContentProvider object. The returned object will not be initialized + * with a Context yet and should not be used to interact with other android APIs. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + */ + public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl, + @NonNull String className) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (ContentProvider) cl.loadClass(className).newInstance(); + } + + /** + * @hide + */ + public static final AppComponentFactory DEFAULT = new AppComponentFactory(); +}
diff --git a/android/app/AppDetailsActivity.java b/android/app/AppDetailsActivity.java new file mode 100644 index 0000000..b71af88 --- /dev/null +++ b/android/app/AppDetailsActivity.java
@@ -0,0 +1,39 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.TestApi; +import android.content.Intent; +import android.os.Bundle; + +/** + * Helper activity that forwards you to app details page. + * + * @hide + */ +@TestApi +public class AppDetailsActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(android.net.Uri.fromParts("package", getPackageName(), null)); + startActivity(intent); + finish(); + } +}
diff --git a/android/app/AppGlobals.java b/android/app/AppGlobals.java new file mode 100644 index 0000000..1f737b6 --- /dev/null +++ b/android/app/AppGlobals.java
@@ -0,0 +1,69 @@ +/* + * 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.UnsupportedAppUsage; +import android.content.pm.IPackageManager; + +/** + * Special private access for certain globals related to a process. + * @hide + */ +public class AppGlobals { + /** + * Return the first Application object made in the process. + * NOTE: Only works on the main thread. + */ + @UnsupportedAppUsage + public static Application getInitialApplication() { + return ActivityThread.currentApplication(); + } + + /** + * Return the package name of the first .apk loaded into the process. + * NOTE: Only works on the main thread. + */ + @UnsupportedAppUsage + public static String getInitialPackage() { + return ActivityThread.currentPackageName(); + } + + /** + * Return the raw interface to the package manager. + * @return The package manager. + */ + @UnsupportedAppUsage + public static IPackageManager getPackageManager() { + return ActivityThread.getPackageManager(); + } + + /** + * Gets the value of an integer core setting. + * + * @param key The setting key. + * @param defaultValue The setting default value. + * @return The core settings. + */ + public static int getIntCoreSetting(String key, int defaultValue) { + ActivityThread currentActivityThread = ActivityThread.currentActivityThread(); + if (currentActivityThread != null) { + return currentActivityThread.getIntCoreSetting(key, defaultValue); + } else { + return defaultValue; + } + } +}
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java new file mode 100644 index 0000000..fb72e65 --- /dev/null +++ b/android/app/AppOpsManager.java
@@ -0,0 +1,5765 @@ +/* + * 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 android.Manifest; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; +import android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.media.AudioAttributes.AttributeUsage; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserManager; +import android.util.ArrayMap; +import android.util.LongSparseArray; +import android.util.LongSparseLongArray; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.Immutable; +import com.android.internal.app.IAppOpsActiveCallback; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; +import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * API for interacting with "application operation" tracking. + * + * <p>This API is not generally intended for third party application developers; most + * features are only available to system applications. + */ +@SystemService(Context.APP_OPS_SERVICE) +public class AppOpsManager { + /** + * <p>App ops allows callers to:</p> + * + * <ul> + * <li> Note when operations are happening, and find out if they are allowed for the current + * caller.</li> + * <li> Disallow specific apps from doing specific operations.</li> + * <li> Collect all of the current information about operations that have been executed or + * are not being allowed.</li> + * <li> Monitor for changes in whether an operation is allowed.</li> + * </ul> + * + * <p>Each operation is identified by a single integer; these integers are a fixed set of + * operations, enumerated by the OP_* constants. + * + * <p></p>When checking operations, the result is a "mode" integer indicating the current + * setting for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute + * the operation but fake its behavior enough so that the caller doesn't crash), + * MODE_ERRORED (throw a SecurityException back to the caller; the normal operation calls + * will do this for you). + */ + + final Context mContext; + + @UnsupportedAppUsage + final IAppOpsService mService; + + @GuardedBy("mModeWatchers") + private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = + new ArrayMap<>(); + + @GuardedBy("mActiveWatchers") + private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers = + new ArrayMap<>(); + + @GuardedBy("mNotedWatchers") + private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers = + new ArrayMap<>(); + + static IBinder sToken; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = { + HISTORICAL_MODE_DISABLED, + HISTORICAL_MODE_ENABLED_ACTIVE, + HISTORICAL_MODE_ENABLED_PASSIVE + }) + public @interface HistoricalMode {} + + /** + * Mode in which app op history is completely disabled. + * @hide + */ + @TestApi + public static final int HISTORICAL_MODE_DISABLED = 0; + + /** + * Mode in which app op history is enabled and app ops performed by apps would + * be tracked. This is the mode in which the feature is completely enabled. + * @hide + */ + @TestApi + public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; + + /** + * Mode in which app op history is enabled but app ops performed by apps would + * not be tracked and the only way to add ops to the history is via explicit calls + * to dedicated APIs. This mode is useful for testing to allow full control of + * the historical content. + * @hide + */ + @TestApi + public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODE_" }, value = { + MODE_ALLOWED, + MODE_IGNORED, + MODE_ERRORED, + MODE_DEFAULT, + MODE_FOREGROUND + }) + public @interface Mode {} + + /** + * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is + * allowed to perform the given operation. + */ + public static final int MODE_ALLOWED = 0; + + /** + * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is + * not allowed to perform the given operation, and this attempt should + * <em>silently fail</em> (it should not cause the app to crash). + */ + public static final int MODE_IGNORED = 1; + + /** + * Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the + * given caller is not allowed to perform the given operation, and this attempt should + * cause it to have a fatal error, typically a {@link SecurityException}. + */ + public static final int MODE_ERRORED = 2; + + /** + * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller should + * use its default security check. This mode is not normally used; it should only be used + * with appop permissions, and callers must explicitly check for it and deal with it. + */ + public static final int MODE_DEFAULT = 3; + + /** + * Special mode that means "allow only when app is in foreground." This is <b>not</b> + * returned from {@link #unsafeCheckOp}, {@link #noteOp}, {@link #startOp}. Rather, + * {@link #unsafeCheckOp} will always return {@link #MODE_ALLOWED} (because it is always + * possible for it to be ultimately allowed, depending on the app's background state), + * and {@link #noteOp} and {@link #startOp} will return {@link #MODE_ALLOWED} when the app + * being checked is currently in the foreground, otherwise {@link #MODE_IGNORED}. + * + * <p>The only place you will this normally see this value is through + * {@link #unsafeCheckOpRaw}, which returns the actual raw mode of the op. Note that because + * you can't know the current state of the app being checked (and it can change at any + * point), you can only treat the result here as an indication that it will vary between + * {@link #MODE_ALLOWED} and {@link #MODE_IGNORED} depending on changes in the background + * state of the app. You thus must always use {@link #noteOp} or {@link #startOp} to do + * the actual check for access to the op.</p> + */ + public static final int MODE_FOREGROUND = 4; + + /** + * Flag for {@link #startWatchingMode(String, String, int, OnOpChangedListener)}: + * Also get reports if the foreground state of an op's uid changes. This only works + * when watching a particular op, not when watching a package. + */ + public static final int WATCH_FOREGROUND_CHANGES = 1 << 0; + + /** + * @hide + */ + public static final String[] MODE_NAMES = new String[] { + "allow", // MODE_ALLOWED + "ignore", // MODE_IGNORED + "deny", // MODE_ERRORED + "default", // MODE_DEFAULT + "foreground", // MODE_FOREGROUND + }; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "UID_STATE_" }, value = { + UID_STATE_PERSISTENT, + UID_STATE_TOP, + UID_STATE_FOREGROUND_SERVICE_LOCATION, + UID_STATE_FOREGROUND_SERVICE, + UID_STATE_FOREGROUND, + UID_STATE_BACKGROUND, + UID_STATE_CACHED + }) + public @interface UidState {} + + /** + * Uid state: The UID is a foreground persistent app. The lower the UID + * state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_PERSISTENT = 100; + + /** + * Uid state: The UID is top foreground app. The lower the UID + * state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_TOP = 200; + + /** + * Uid state: The UID is running a foreground service of location type. + * The lower the UID state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_FOREGROUND_SERVICE_LOCATION = 300; + + /** + * Uid state: The UID is running a foreground service. The lower the UID + * state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_FOREGROUND_SERVICE = 400; + + /** + * The max, which is min priority, UID state for which any app op + * would be considered as performed in the foreground. + * @hide + */ + public static final int UID_STATE_MAX_LAST_NON_RESTRICTED = UID_STATE_FOREGROUND_SERVICE; + + /** + * Uid state: The UID is a foreground app. The lower the UID + * state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_FOREGROUND = 500; + + /** + * Uid state: The UID is a background app. The lower the UID + * state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_BACKGROUND = 600; + + /** + * Uid state: The UID is a cached app. The lower the UID + * state the more important the UID is for the user. + * @hide + */ + @TestApi + @SystemApi + public static final int UID_STATE_CACHED = 700; + + /** + * Uid state: The UID state with the highest priority. + * @hide + */ + public static final int MAX_PRIORITY_UID_STATE = UID_STATE_PERSISTENT; + + /** + * Uid state: The UID state with the lowest priority. + * @hide + */ + public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED; + + /** + * Resolves the first unrestricted state given an app op. Location is + * special as we want to allow its access only if a dedicated location + * foreground service is running. For other ops we consider any foreground + * service as a foreground state. + * + * @param op The op to resolve. + * @return The last restricted UID state. + * + * @hide + */ + public static int resolveFirstUnrestrictedUidState(int op) { + switch (op) { + case OP_FINE_LOCATION: + case OP_COARSE_LOCATION: + case OP_MONITOR_LOCATION: + case OP_MONITOR_HIGH_POWER_LOCATION: { + return UID_STATE_FOREGROUND_SERVICE_LOCATION; + } + } + return UID_STATE_FOREGROUND_SERVICE; + } + + /** + * Resolves the last restricted state given an app op. Location is + * special as we want to allow its access only if a dedicated location + * foreground service is running. For other ops we consider any foreground + * service as a foreground state. + * + * @param op The op to resolve. + * @return The last restricted UID state. + * + * @hide + */ + public static int resolveLastRestrictedUidState(int op) { + switch (op) { + case OP_FINE_LOCATION: + case OP_COARSE_LOCATION: { + return UID_STATE_FOREGROUND_SERVICE; + } + } + return UID_STATE_FOREGROUND; + } + + /** @hide Note: Keep these sorted */ + public static final int[] UID_STATES = { + UID_STATE_PERSISTENT, + UID_STATE_TOP, + UID_STATE_FOREGROUND_SERVICE_LOCATION, + UID_STATE_FOREGROUND_SERVICE, + UID_STATE_FOREGROUND, + UID_STATE_BACKGROUND, + UID_STATE_CACHED + }; + + /** @hide */ + public static String getUidStateName(@UidState int uidState) { + switch (uidState) { + case UID_STATE_PERSISTENT: + return "pers"; + case UID_STATE_TOP: + return "top"; + case UID_STATE_FOREGROUND_SERVICE_LOCATION: + return "fgsvcl"; + case UID_STATE_FOREGROUND_SERVICE: + return "fgsvc"; + case UID_STATE_FOREGROUND: + return "fg"; + case UID_STATE_BACKGROUND: + return "bg"; + case UID_STATE_CACHED: + return "cch"; + default: + return "unknown"; + } + } + + /** + * Flag: non proxy operations. These are operations + * performed on behalf of the app itself and not on behalf of + * another one. + * + * @hide + */ + @TestApi + @SystemApi + public static final int OP_FLAG_SELF = 0x1; + + /** + * Flag: trusted proxy operations. These are operations + * performed on behalf of another app by a trusted app. + * Which is work a trusted app blames on another app. + * + * @hide + */ + @TestApi + @SystemApi + public static final int OP_FLAG_TRUSTED_PROXY = 0x2; + + /** + * Flag: untrusted proxy operations. These are operations + * performed on behalf of another app by an untrusted app. + * Which is work an untrusted app blames on another app. + * + * @hide + */ + @TestApi + @SystemApi + public static final int OP_FLAG_UNTRUSTED_PROXY = 0x4; + + /** + * Flag: trusted proxied operations. These are operations + * performed by a trusted other app on behalf of an app. + * Which is work an app was blamed for by a trusted app. + * + * @hide + */ + @TestApi + @SystemApi + public static final int OP_FLAG_TRUSTED_PROXIED = 0x8; + + /** + * Flag: untrusted proxied operations. These are operations + * performed by an untrusted other app on behalf of an app. + * Which is work an app was blamed for by an untrusted app. + * + * @hide + */ + @TestApi + @SystemApi + public static final int OP_FLAG_UNTRUSTED_PROXIED = 0x10; + + /** + * Flags: all operations. These include operations matched + * by {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. + * + * @hide + */ + @TestApi + @SystemApi + public static final int OP_FLAGS_ALL = + OP_FLAG_SELF + | OP_FLAG_TRUSTED_PROXY + | OP_FLAG_UNTRUSTED_PROXY + | OP_FLAG_TRUSTED_PROXIED + | OP_FLAG_UNTRUSTED_PROXIED; + + /** + * Flags: all trusted operations which is ones either the app did {@link #OP_FLAG_SELF}, + * or it was blamed for by a trusted app {@link #OP_FLAG_TRUSTED_PROXIED}, or ones the + * app if untrusted blamed on other apps {@link #OP_FLAG_UNTRUSTED_PROXY}. + * + * @hide + */ + @SystemApi + public static final int OP_FLAGS_ALL_TRUSTED = AppOpsManager.OP_FLAG_SELF + | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY + | AppOpsManager.OP_FLAG_TRUSTED_PROXIED; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "FLAG_" }, value = { + OP_FLAG_SELF, + OP_FLAG_TRUSTED_PROXY, + OP_FLAG_UNTRUSTED_PROXY, + OP_FLAG_TRUSTED_PROXIED, + OP_FLAG_UNTRUSTED_PROXIED + }) + public @interface OpFlags {} + + + /** @hide */ + public static final String getFlagName(@OpFlags int flag) { + switch (flag) { + case OP_FLAG_SELF: + return "s"; + case OP_FLAG_TRUSTED_PROXY: + return "tp"; + case OP_FLAG_UNTRUSTED_PROXY: + return "up"; + case OP_FLAG_TRUSTED_PROXIED: + return "tpd"; + case OP_FLAG_UNTRUSTED_PROXIED: + return "upd"; + default: + return "unknown"; + } + } + + private static final int UID_STATE_OFFSET = 31; + private static final int FLAGS_MASK = 0xFFFFFFFF; + + /** + * Key for a data bucket storing app op state. The bucket + * is composed of the uid state and state flags. This way + * we can query data for given uid state and a set of flags where + * the flags control which type of data to get. For example, + * one can get the ops an app did on behalf of other apps + * while in the background. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + public @interface DataBucketKey { + } + + /** @hide */ + public static String keyToString(@DataBucketKey long key) { + final int uidState = extractUidStateFromKey(key); + final int flags = extractFlagsFromKey(key); + return "[" + getUidStateName(uidState) + "-" + flagsToString(flags) + "]"; + } + + /** @hide */ + public static @DataBucketKey long makeKey(@UidState int uidState, @OpFlags int flags) { + return ((long) uidState << UID_STATE_OFFSET) | flags; + } + + /** @hide */ + public static int extractUidStateFromKey(@DataBucketKey long key) { + return (int) (key >> UID_STATE_OFFSET); + } + + /** @hide */ + public static int extractFlagsFromKey(@DataBucketKey long key) { + return (int) (key & FLAGS_MASK); + } + + /** @hide */ + public static String flagsToString(@OpFlags int flags) { + final StringBuilder flagsBuilder = new StringBuilder(); + while (flags != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(flags); + flags &= ~flag; + if (flagsBuilder.length() > 0) { + flagsBuilder.append('|'); + } + flagsBuilder.append(getFlagName(flag)); + } + return flagsBuilder.toString(); + } + + // when adding one of these: + // - increment _NUM_OP + // - define an OPSTR_* constant (marked as @SystemApi) + // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault + // - add descriptive strings to Settings/res/values/arrays.xml + // - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app) + + /** @hide No operation specified. */ + @UnsupportedAppUsage + public static final int OP_NONE = -1; + /** @hide Access to coarse location information. */ + @TestApi + public static final int OP_COARSE_LOCATION = 0; + /** @hide Access to fine location information. */ + @UnsupportedAppUsage + public static final int OP_FINE_LOCATION = 1; + /** @hide Causing GPS to run. */ + @UnsupportedAppUsage + public static final int OP_GPS = 2; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_VIBRATE = 3; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_READ_CONTACTS = 4; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_CONTACTS = 5; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_READ_CALL_LOG = 6; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_CALL_LOG = 7; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_READ_CALENDAR = 8; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_CALENDAR = 9; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WIFI_SCAN = 10; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_POST_NOTIFICATION = 11; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_NEIGHBORING_CELLS = 12; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_CALL_PHONE = 13; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_READ_SMS = 14; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_SMS = 15; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_RECEIVE_SMS = 16; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_RECEIVE_EMERGECY_SMS = 17; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_RECEIVE_MMS = 18; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_RECEIVE_WAP_PUSH = 19; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_SEND_SMS = 20; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_READ_ICC_SMS = 21; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_ICC_SMS = 22; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_SETTINGS = 23; + /** @hide Required to draw on top of other apps. */ + @TestApi + public static final int OP_SYSTEM_ALERT_WINDOW = 24; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_ACCESS_NOTIFICATIONS = 25; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_CAMERA = 26; + /** @hide */ + @TestApi + public static final int OP_RECORD_AUDIO = 27; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_PLAY_AUDIO = 28; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_READ_CLIPBOARD = 29; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WRITE_CLIPBOARD = 30; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_TAKE_MEDIA_BUTTONS = 31; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_TAKE_AUDIO_FOCUS = 32; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_MASTER_VOLUME = 33; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_VOICE_VOLUME = 34; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_RING_VOLUME = 35; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_MEDIA_VOLUME = 36; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_ALARM_VOLUME = 37; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_NOTIFICATION_VOLUME = 38; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_BLUETOOTH_VOLUME = 39; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_WAKE_LOCK = 40; + /** @hide Continually monitoring location data. */ + @UnsupportedAppUsage + public static final int OP_MONITOR_LOCATION = 41; + /** @hide Continually monitoring location data with a relatively high power request. */ + @UnsupportedAppUsage + public static final int OP_MONITOR_HIGH_POWER_LOCATION = 42; + /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */ + @UnsupportedAppUsage + public static final int OP_GET_USAGE_STATS = 43; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_MUTE_MICROPHONE = 44; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_TOAST_WINDOW = 45; + /** @hide Capture the device's display contents and/or audio */ + @UnsupportedAppUsage + public static final int OP_PROJECT_MEDIA = 46; + /** @hide Activate a VPN connection without user intervention. */ + @UnsupportedAppUsage + public static final int OP_ACTIVATE_VPN = 47; + /** @hide Access the WallpaperManagerAPI to write wallpapers. */ + @UnsupportedAppUsage + public static final int OP_WRITE_WALLPAPER = 48; + /** @hide Received the assist structure from an app. */ + @UnsupportedAppUsage + public static final int OP_ASSIST_STRUCTURE = 49; + /** @hide Received a screenshot from assist. */ + @UnsupportedAppUsage + public static final int OP_ASSIST_SCREENSHOT = 50; + /** @hide Read the phone state. */ + @UnsupportedAppUsage + public static final int OP_READ_PHONE_STATE = 51; + /** @hide Add voicemail messages to the voicemail content provider. */ + @UnsupportedAppUsage + public static final int OP_ADD_VOICEMAIL = 52; + /** @hide Access APIs for SIP calling over VOIP or WiFi. */ + @UnsupportedAppUsage + public static final int OP_USE_SIP = 53; + /** @hide Intercept outgoing calls. */ + @UnsupportedAppUsage + public static final int OP_PROCESS_OUTGOING_CALLS = 54; + /** @hide User the fingerprint API. */ + @UnsupportedAppUsage + public static final int OP_USE_FINGERPRINT = 55; + /** @hide Access to body sensors such as heart rate, etc. */ + @UnsupportedAppUsage + public static final int OP_BODY_SENSORS = 56; + /** @hide Read previously received cell broadcast messages. */ + @UnsupportedAppUsage + public static final int OP_READ_CELL_BROADCASTS = 57; + /** @hide Inject mock location into the system. */ + @UnsupportedAppUsage + public static final int OP_MOCK_LOCATION = 58; + /** @hide Read external storage. */ + @UnsupportedAppUsage + public static final int OP_READ_EXTERNAL_STORAGE = 59; + /** @hide Write external storage. */ + @UnsupportedAppUsage + public static final int OP_WRITE_EXTERNAL_STORAGE = 60; + /** @hide Turned on the screen. */ + @UnsupportedAppUsage + public static final int OP_TURN_SCREEN_ON = 61; + /** @hide Get device accounts. */ + @UnsupportedAppUsage + public static final int OP_GET_ACCOUNTS = 62; + /** @hide Control whether an application is allowed to run in the background. */ + @UnsupportedAppUsage + public static final int OP_RUN_IN_BACKGROUND = 63; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = 64; + /** @hide Read the phone number. */ + @UnsupportedAppUsage + public static final int OP_READ_PHONE_NUMBERS = 65; + /** @hide Request package installs through package installer */ + @UnsupportedAppUsage + public static final int OP_REQUEST_INSTALL_PACKAGES = 66; + /** @hide Enter picture-in-picture. */ + @UnsupportedAppUsage + public static final int OP_PICTURE_IN_PICTURE = 67; + /** @hide Instant app start foreground service. */ + @UnsupportedAppUsage + public static final int OP_INSTANT_APP_START_FOREGROUND = 68; + /** @hide Answer incoming phone calls */ + @UnsupportedAppUsage + public static final int OP_ANSWER_PHONE_CALLS = 69; + /** @hide Run jobs when in background */ + @UnsupportedAppUsage + public static final int OP_RUN_ANY_IN_BACKGROUND = 70; + /** @hide Change Wi-Fi connectivity state */ + @UnsupportedAppUsage + public static final int OP_CHANGE_WIFI_STATE = 71; + /** @hide Request package deletion through package installer */ + @UnsupportedAppUsage + public static final int OP_REQUEST_DELETE_PACKAGES = 72; + /** @hide Bind an accessibility service. */ + @UnsupportedAppUsage + public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73; + /** @hide Continue handover of a call from another app */ + @UnsupportedAppUsage + public static final int OP_ACCEPT_HANDOVER = 74; + /** @hide Create and Manage IPsec Tunnels */ + @UnsupportedAppUsage + public static final int OP_MANAGE_IPSEC_TUNNELS = 75; + /** @hide Any app start foreground service. */ + @TestApi + public static final int OP_START_FOREGROUND = 76; + /** @hide */ + @UnsupportedAppUsage + public static final int OP_BLUETOOTH_SCAN = 77; + /** @hide Use the BiometricPrompt/BiometricManager APIs. */ + public static final int OP_USE_BIOMETRIC = 78; + /** @hide Physical activity recognition. */ + public static final int OP_ACTIVITY_RECOGNITION = 79; + /** @hide Financial app sms read. */ + public static final int OP_SMS_FINANCIAL_TRANSACTIONS = 80; + /** @hide Read media of audio type. */ + public static final int OP_READ_MEDIA_AUDIO = 81; + /** @hide Write media of audio type. */ + public static final int OP_WRITE_MEDIA_AUDIO = 82; + /** @hide Read media of video type. */ + public static final int OP_READ_MEDIA_VIDEO = 83; + /** @hide Write media of video type. */ + public static final int OP_WRITE_MEDIA_VIDEO = 84; + /** @hide Read media of image type. */ + public static final int OP_READ_MEDIA_IMAGES = 85; + /** @hide Write media of image type. */ + public static final int OP_WRITE_MEDIA_IMAGES = 86; + /** @hide Has a legacy (non-isolated) view of storage. */ + public static final int OP_LEGACY_STORAGE = 87; + /** @hide Accessing accessibility features */ + public static final int OP_ACCESS_ACCESSIBILITY = 88; + /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */ + public static final int OP_READ_DEVICE_IDENTIFIERS = 89; + /** @hide */ + @UnsupportedAppUsage + public static final int _NUM_OP = 90; + + /** Access to coarse location information. */ + public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; + /** Access to fine location information. */ + public static final String OPSTR_FINE_LOCATION = + "android:fine_location"; + /** Continually monitoring location data. */ + public static final String OPSTR_MONITOR_LOCATION + = "android:monitor_location"; + /** Continually monitoring location data with a relatively high power request. */ + public static final String OPSTR_MONITOR_HIGH_POWER_LOCATION + = "android:monitor_location_high_power"; + /** Access to {@link android.app.usage.UsageStatsManager}. */ + public static final String OPSTR_GET_USAGE_STATS + = "android:get_usage_stats"; + /** Activate a VPN connection without user intervention. @hide */ + @SystemApi @TestApi + public static final String OPSTR_ACTIVATE_VPN + = "android:activate_vpn"; + /** Allows an application to read the user's contacts data. */ + public static final String OPSTR_READ_CONTACTS + = "android:read_contacts"; + /** Allows an application to write to the user's contacts data. */ + public static final String OPSTR_WRITE_CONTACTS + = "android:write_contacts"; + /** Allows an application to read the user's call log. */ + public static final String OPSTR_READ_CALL_LOG + = "android:read_call_log"; + /** Allows an application to write to the user's call log. */ + public static final String OPSTR_WRITE_CALL_LOG + = "android:write_call_log"; + /** Allows an application to read the user's calendar data. */ + public static final String OPSTR_READ_CALENDAR + = "android:read_calendar"; + /** Allows an application to write to the user's calendar data. */ + public static final String OPSTR_WRITE_CALENDAR + = "android:write_calendar"; + /** Allows an application to initiate a phone call. */ + public static final String OPSTR_CALL_PHONE + = "android:call_phone"; + /** Allows an application to read SMS messages. */ + public static final String OPSTR_READ_SMS + = "android:read_sms"; + /** Allows an application to receive SMS messages. */ + public static final String OPSTR_RECEIVE_SMS + = "android:receive_sms"; + /** Allows an application to receive MMS messages. */ + public static final String OPSTR_RECEIVE_MMS + = "android:receive_mms"; + /** Allows an application to receive WAP push messages. */ + public static final String OPSTR_RECEIVE_WAP_PUSH + = "android:receive_wap_push"; + /** Allows an application to send SMS messages. */ + public static final String OPSTR_SEND_SMS + = "android:send_sms"; + /** Required to be able to access the camera device. */ + public static final String OPSTR_CAMERA + = "android:camera"; + /** Required to be able to access the microphone device. */ + public static final String OPSTR_RECORD_AUDIO + = "android:record_audio"; + /** Required to access phone state related information. */ + public static final String OPSTR_READ_PHONE_STATE + = "android:read_phone_state"; + /** Required to access phone state related information. */ + public static final String OPSTR_ADD_VOICEMAIL + = "android:add_voicemail"; + /** Access APIs for SIP calling over VOIP or WiFi */ + public static final String OPSTR_USE_SIP + = "android:use_sip"; + /** Access APIs for diverting outgoing calls */ + public static final String OPSTR_PROCESS_OUTGOING_CALLS + = "android:process_outgoing_calls"; + /** Use the fingerprint API. */ + public static final String OPSTR_USE_FINGERPRINT + = "android:use_fingerprint"; + /** Access to body sensors such as heart rate, etc. */ + public static final String OPSTR_BODY_SENSORS + = "android:body_sensors"; + /** Read previously received cell broadcast messages. */ + public static final String OPSTR_READ_CELL_BROADCASTS + = "android:read_cell_broadcasts"; + /** Inject mock location into the system. */ + public static final String OPSTR_MOCK_LOCATION + = "android:mock_location"; + /** Read external storage. */ + public static final String OPSTR_READ_EXTERNAL_STORAGE + = "android:read_external_storage"; + /** Write external storage. */ + public static final String OPSTR_WRITE_EXTERNAL_STORAGE + = "android:write_external_storage"; + /** Required to draw on top of other apps. */ + public static final String OPSTR_SYSTEM_ALERT_WINDOW + = "android:system_alert_window"; + /** Required to write/modify/update system settingss. */ + public static final String OPSTR_WRITE_SETTINGS + = "android:write_settings"; + /** @hide Get device accounts. */ + @SystemApi @TestApi + public static final String OPSTR_GET_ACCOUNTS + = "android:get_accounts"; + public static final String OPSTR_READ_PHONE_NUMBERS + = "android:read_phone_numbers"; + /** Access to picture-in-picture. */ + public static final String OPSTR_PICTURE_IN_PICTURE + = "android:picture_in_picture"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_INSTANT_APP_START_FOREGROUND + = "android:instant_app_start_foreground"; + /** Answer incoming phone calls */ + public static final String OPSTR_ANSWER_PHONE_CALLS + = "android:answer_phone_calls"; + /** + * Accept call handover + * @hide + */ + @SystemApi @TestApi + public static final String OPSTR_ACCEPT_HANDOVER + = "android:accept_handover"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_GPS = "android:gps"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_VIBRATE = "android:vibrate"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WIFI_SCAN = "android:wifi_scan"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_POST_NOTIFICATION = "android:post_notification"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_SMS = "android:write_sms"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = + "android:receive_emergency_broadcast"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_NOTIFICATION_VOLUME = + "android:audio_notification_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WAKE_LOCK = "android:wake_lock"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TOAST_WINDOW = "android:toast_window"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_ASSIST_STRUCTURE = "android:assist_structure"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_AUDIO_ACCESSIBILITY_VOLUME = + "android:audio_accessibility_volume"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = + "android:bind_accessibility_service"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels"; + /** @hide */ + @SystemApi @TestApi + public static final String OPSTR_START_FOREGROUND = "android:start_foreground"; + /** @hide */ + public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan"; + + /** @hide Use the BiometricPrompt/BiometricManager APIs. */ + public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric"; + + /** @hide Recognize physical activity. */ + public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition"; + + /** @hide Financial app read sms. */ + public static final String OPSTR_SMS_FINANCIAL_TRANSACTIONS = + "android:sms_financial_transactions"; + + /** @hide Read media of audio type. */ + public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; + /** @hide Write media of audio type. */ + public static final String OPSTR_WRITE_MEDIA_AUDIO = "android:write_media_audio"; + /** @hide Read media of video type. */ + public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; + /** @hide Write media of video type. */ + public static final String OPSTR_WRITE_MEDIA_VIDEO = "android:write_media_video"; + /** @hide Read media of image type. */ + public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; + /** @hide Write media of image type. */ + public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images"; + /** @hide Has a legacy (non-isolated) view of storage. */ + @TestApi + @SystemApi + public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage"; + /** @hide Interact with accessibility. */ + @SystemApi + public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; + /** @hide Read device identifiers */ + public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers"; + + // Warning: If an permission is added here it also has to be added to + // com.android.packageinstaller.permission.utils.EventLogger + private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = { + // RUNTIME PERMISSIONS + // Contacts + OP_READ_CONTACTS, + OP_WRITE_CONTACTS, + OP_GET_ACCOUNTS, + // Calendar + OP_READ_CALENDAR, + OP_WRITE_CALENDAR, + // SMS + OP_SEND_SMS, + OP_RECEIVE_SMS, + OP_READ_SMS, + OP_RECEIVE_WAP_PUSH, + OP_RECEIVE_MMS, + OP_READ_CELL_BROADCASTS, + // Storage + OP_READ_EXTERNAL_STORAGE, + OP_WRITE_EXTERNAL_STORAGE, + // Location + OP_COARSE_LOCATION, + OP_FINE_LOCATION, + // Phone + OP_READ_PHONE_STATE, + OP_READ_PHONE_NUMBERS, + OP_CALL_PHONE, + OP_READ_CALL_LOG, + OP_WRITE_CALL_LOG, + OP_ADD_VOICEMAIL, + OP_USE_SIP, + OP_PROCESS_OUTGOING_CALLS, + OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, + // Microphone + OP_RECORD_AUDIO, + // Camera + OP_CAMERA, + // Body sensors + OP_BODY_SENSORS, + // Activity recognition + OP_ACTIVITY_RECOGNITION, + // Aural + OP_READ_MEDIA_AUDIO, + OP_WRITE_MEDIA_AUDIO, + // Visual + OP_READ_MEDIA_VIDEO, + OP_WRITE_MEDIA_VIDEO, + OP_READ_MEDIA_IMAGES, + OP_WRITE_MEDIA_IMAGES, + + // APPOP PERMISSIONS + OP_ACCESS_NOTIFICATIONS, + OP_SYSTEM_ALERT_WINDOW, + OP_WRITE_SETTINGS, + OP_REQUEST_INSTALL_PACKAGES, + OP_START_FOREGROUND, + OP_SMS_FINANCIAL_TRANSACTIONS, + }; + + /** + * This maps each operation to the operation that serves as the + * switch to determine whether it is allowed. Generally this is + * a 1:1 mapping, but for some things (like location) that have + * multiple low-level operations being tracked that should be + * presented to the user as one switch then this can be used to + * make them all controlled by the same single operation. + */ + private static int[] sOpToSwitch = new int[] { + OP_COARSE_LOCATION, // COARSE_LOCATION + OP_COARSE_LOCATION, // FINE_LOCATION + OP_COARSE_LOCATION, // GPS + OP_VIBRATE, // VIBRATE + OP_READ_CONTACTS, // READ_CONTACTS + OP_WRITE_CONTACTS, // WRITE_CONTACTS + OP_READ_CALL_LOG, // READ_CALL_LOG + OP_WRITE_CALL_LOG, // WRITE_CALL_LOG + OP_READ_CALENDAR, // READ_CALENDAR + OP_WRITE_CALENDAR, // WRITE_CALENDAR + OP_COARSE_LOCATION, // WIFI_SCAN + OP_POST_NOTIFICATION, // POST_NOTIFICATION + OP_COARSE_LOCATION, // NEIGHBORING_CELLS + OP_CALL_PHONE, // CALL_PHONE + OP_READ_SMS, // READ_SMS + OP_WRITE_SMS, // WRITE_SMS + OP_RECEIVE_SMS, // RECEIVE_SMS + OP_RECEIVE_SMS, // RECEIVE_EMERGECY_SMS + OP_RECEIVE_MMS, // RECEIVE_MMS + OP_RECEIVE_WAP_PUSH, // RECEIVE_WAP_PUSH + OP_SEND_SMS, // SEND_SMS + OP_READ_SMS, // READ_ICC_SMS + OP_WRITE_SMS, // WRITE_ICC_SMS + OP_WRITE_SETTINGS, // WRITE_SETTINGS + OP_SYSTEM_ALERT_WINDOW, // SYSTEM_ALERT_WINDOW + OP_ACCESS_NOTIFICATIONS, // ACCESS_NOTIFICATIONS + OP_CAMERA, // CAMERA + OP_RECORD_AUDIO, // RECORD_AUDIO + OP_PLAY_AUDIO, // PLAY_AUDIO + OP_READ_CLIPBOARD, // READ_CLIPBOARD + OP_WRITE_CLIPBOARD, // WRITE_CLIPBOARD + OP_TAKE_MEDIA_BUTTONS, // TAKE_MEDIA_BUTTONS + OP_TAKE_AUDIO_FOCUS, // TAKE_AUDIO_FOCUS + OP_AUDIO_MASTER_VOLUME, // AUDIO_MASTER_VOLUME + OP_AUDIO_VOICE_VOLUME, // AUDIO_VOICE_VOLUME + OP_AUDIO_RING_VOLUME, // AUDIO_RING_VOLUME + OP_AUDIO_MEDIA_VOLUME, // AUDIO_MEDIA_VOLUME + OP_AUDIO_ALARM_VOLUME, // AUDIO_ALARM_VOLUME + OP_AUDIO_NOTIFICATION_VOLUME, // AUDIO_NOTIFICATION_VOLUME + OP_AUDIO_BLUETOOTH_VOLUME, // AUDIO_BLUETOOTH_VOLUME + OP_WAKE_LOCK, // WAKE_LOCK + OP_COARSE_LOCATION, // MONITOR_LOCATION + OP_COARSE_LOCATION, // MONITOR_HIGH_POWER_LOCATION + OP_GET_USAGE_STATS, // GET_USAGE_STATS + OP_MUTE_MICROPHONE, // MUTE_MICROPHONE + OP_TOAST_WINDOW, // TOAST_WINDOW + OP_PROJECT_MEDIA, // PROJECT_MEDIA + OP_ACTIVATE_VPN, // ACTIVATE_VPN + OP_WRITE_WALLPAPER, // WRITE_WALLPAPER + OP_ASSIST_STRUCTURE, // ASSIST_STRUCTURE + OP_ASSIST_SCREENSHOT, // ASSIST_SCREENSHOT + OP_READ_PHONE_STATE, // READ_PHONE_STATE + OP_ADD_VOICEMAIL, // ADD_VOICEMAIL + OP_USE_SIP, // USE_SIP + OP_PROCESS_OUTGOING_CALLS, // PROCESS_OUTGOING_CALLS + OP_USE_FINGERPRINT, // USE_FINGERPRINT + OP_BODY_SENSORS, // BODY_SENSORS + OP_READ_CELL_BROADCASTS, // READ_CELL_BROADCASTS + OP_MOCK_LOCATION, // MOCK_LOCATION + OP_READ_EXTERNAL_STORAGE, // READ_EXTERNAL_STORAGE + OP_WRITE_EXTERNAL_STORAGE, // WRITE_EXTERNAL_STORAGE + OP_TURN_SCREEN_ON, // TURN_SCREEN_ON + OP_GET_ACCOUNTS, // GET_ACCOUNTS + OP_RUN_IN_BACKGROUND, // RUN_IN_BACKGROUND + OP_AUDIO_ACCESSIBILITY_VOLUME, // AUDIO_ACCESSIBILITY_VOLUME + OP_READ_PHONE_NUMBERS, // READ_PHONE_NUMBERS + OP_REQUEST_INSTALL_PACKAGES, // REQUEST_INSTALL_PACKAGES + OP_PICTURE_IN_PICTURE, // ENTER_PICTURE_IN_PICTURE_ON_HIDE + OP_INSTANT_APP_START_FOREGROUND, // INSTANT_APP_START_FOREGROUND + OP_ANSWER_PHONE_CALLS, // ANSWER_PHONE_CALLS + OP_RUN_ANY_IN_BACKGROUND, // OP_RUN_ANY_IN_BACKGROUND + OP_CHANGE_WIFI_STATE, // OP_CHANGE_WIFI_STATE + OP_REQUEST_DELETE_PACKAGES, // OP_REQUEST_DELETE_PACKAGES + OP_BIND_ACCESSIBILITY_SERVICE, // OP_BIND_ACCESSIBILITY_SERVICE + OP_ACCEPT_HANDOVER, // ACCEPT_HANDOVER + OP_MANAGE_IPSEC_TUNNELS, // MANAGE_IPSEC_HANDOVERS + OP_START_FOREGROUND, // START_FOREGROUND + OP_COARSE_LOCATION, // BLUETOOTH_SCAN + OP_USE_BIOMETRIC, // BIOMETRIC + OP_ACTIVITY_RECOGNITION, // ACTIVITY_RECOGNITION + OP_SMS_FINANCIAL_TRANSACTIONS, // SMS_FINANCIAL_TRANSACTIONS + OP_READ_MEDIA_AUDIO, // READ_MEDIA_AUDIO + OP_WRITE_MEDIA_AUDIO, // WRITE_MEDIA_AUDIO + OP_READ_MEDIA_VIDEO, // READ_MEDIA_VIDEO + OP_WRITE_MEDIA_VIDEO, // WRITE_MEDIA_VIDEO + OP_READ_MEDIA_IMAGES, // READ_MEDIA_IMAGES + OP_WRITE_MEDIA_IMAGES, // WRITE_MEDIA_IMAGES + OP_LEGACY_STORAGE, // LEGACY_STORAGE + OP_ACCESS_ACCESSIBILITY, // ACCESS_ACCESSIBILITY + OP_READ_DEVICE_IDENTIFIERS, // READ_DEVICE_IDENTIFIERS + }; + + /** + * This maps each operation to the public string constant for it. + */ + private static String[] sOpToString = new String[]{ + OPSTR_COARSE_LOCATION, + OPSTR_FINE_LOCATION, + OPSTR_GPS, + OPSTR_VIBRATE, + OPSTR_READ_CONTACTS, + OPSTR_WRITE_CONTACTS, + OPSTR_READ_CALL_LOG, + OPSTR_WRITE_CALL_LOG, + OPSTR_READ_CALENDAR, + OPSTR_WRITE_CALENDAR, + OPSTR_WIFI_SCAN, + OPSTR_POST_NOTIFICATION, + OPSTR_NEIGHBORING_CELLS, + OPSTR_CALL_PHONE, + OPSTR_READ_SMS, + OPSTR_WRITE_SMS, + OPSTR_RECEIVE_SMS, + OPSTR_RECEIVE_EMERGENCY_BROADCAST, + OPSTR_RECEIVE_MMS, + OPSTR_RECEIVE_WAP_PUSH, + OPSTR_SEND_SMS, + OPSTR_READ_ICC_SMS, + OPSTR_WRITE_ICC_SMS, + OPSTR_WRITE_SETTINGS, + OPSTR_SYSTEM_ALERT_WINDOW, + OPSTR_ACCESS_NOTIFICATIONS, + OPSTR_CAMERA, + OPSTR_RECORD_AUDIO, + OPSTR_PLAY_AUDIO, + OPSTR_READ_CLIPBOARD, + OPSTR_WRITE_CLIPBOARD, + OPSTR_TAKE_MEDIA_BUTTONS, + OPSTR_TAKE_AUDIO_FOCUS, + OPSTR_AUDIO_MASTER_VOLUME, + OPSTR_AUDIO_VOICE_VOLUME, + OPSTR_AUDIO_RING_VOLUME, + OPSTR_AUDIO_MEDIA_VOLUME, + OPSTR_AUDIO_ALARM_VOLUME, + OPSTR_AUDIO_NOTIFICATION_VOLUME, + OPSTR_AUDIO_BLUETOOTH_VOLUME, + OPSTR_WAKE_LOCK, + OPSTR_MONITOR_LOCATION, + OPSTR_MONITOR_HIGH_POWER_LOCATION, + OPSTR_GET_USAGE_STATS, + OPSTR_MUTE_MICROPHONE, + OPSTR_TOAST_WINDOW, + OPSTR_PROJECT_MEDIA, + OPSTR_ACTIVATE_VPN, + OPSTR_WRITE_WALLPAPER, + OPSTR_ASSIST_STRUCTURE, + OPSTR_ASSIST_SCREENSHOT, + OPSTR_READ_PHONE_STATE, + OPSTR_ADD_VOICEMAIL, + OPSTR_USE_SIP, + OPSTR_PROCESS_OUTGOING_CALLS, + OPSTR_USE_FINGERPRINT, + OPSTR_BODY_SENSORS, + OPSTR_READ_CELL_BROADCASTS, + OPSTR_MOCK_LOCATION, + OPSTR_READ_EXTERNAL_STORAGE, + OPSTR_WRITE_EXTERNAL_STORAGE, + OPSTR_TURN_SCREEN_ON, + OPSTR_GET_ACCOUNTS, + OPSTR_RUN_IN_BACKGROUND, + OPSTR_AUDIO_ACCESSIBILITY_VOLUME, + OPSTR_READ_PHONE_NUMBERS, + OPSTR_REQUEST_INSTALL_PACKAGES, + OPSTR_PICTURE_IN_PICTURE, + OPSTR_INSTANT_APP_START_FOREGROUND, + OPSTR_ANSWER_PHONE_CALLS, + OPSTR_RUN_ANY_IN_BACKGROUND, + OPSTR_CHANGE_WIFI_STATE, + OPSTR_REQUEST_DELETE_PACKAGES, + OPSTR_BIND_ACCESSIBILITY_SERVICE, + OPSTR_ACCEPT_HANDOVER, + OPSTR_MANAGE_IPSEC_TUNNELS, + OPSTR_START_FOREGROUND, + OPSTR_BLUETOOTH_SCAN, + OPSTR_USE_BIOMETRIC, + OPSTR_ACTIVITY_RECOGNITION, + OPSTR_SMS_FINANCIAL_TRANSACTIONS, + OPSTR_READ_MEDIA_AUDIO, + OPSTR_WRITE_MEDIA_AUDIO, + OPSTR_READ_MEDIA_VIDEO, + OPSTR_WRITE_MEDIA_VIDEO, + OPSTR_READ_MEDIA_IMAGES, + OPSTR_WRITE_MEDIA_IMAGES, + OPSTR_LEGACY_STORAGE, + OPSTR_ACCESS_ACCESSIBILITY, + OPSTR_READ_DEVICE_IDENTIFIERS, + }; + + /** + * This provides a simple name for each operation to be used + * in debug output. + */ + private static String[] sOpNames = new String[] { + "COARSE_LOCATION", + "FINE_LOCATION", + "GPS", + "VIBRATE", + "READ_CONTACTS", + "WRITE_CONTACTS", + "READ_CALL_LOG", + "WRITE_CALL_LOG", + "READ_CALENDAR", + "WRITE_CALENDAR", + "WIFI_SCAN", + "POST_NOTIFICATION", + "NEIGHBORING_CELLS", + "CALL_PHONE", + "READ_SMS", + "WRITE_SMS", + "RECEIVE_SMS", + "RECEIVE_EMERGECY_SMS", + "RECEIVE_MMS", + "RECEIVE_WAP_PUSH", + "SEND_SMS", + "READ_ICC_SMS", + "WRITE_ICC_SMS", + "WRITE_SETTINGS", + "SYSTEM_ALERT_WINDOW", + "ACCESS_NOTIFICATIONS", + "CAMERA", + "RECORD_AUDIO", + "PLAY_AUDIO", + "READ_CLIPBOARD", + "WRITE_CLIPBOARD", + "TAKE_MEDIA_BUTTONS", + "TAKE_AUDIO_FOCUS", + "AUDIO_MASTER_VOLUME", + "AUDIO_VOICE_VOLUME", + "AUDIO_RING_VOLUME", + "AUDIO_MEDIA_VOLUME", + "AUDIO_ALARM_VOLUME", + "AUDIO_NOTIFICATION_VOLUME", + "AUDIO_BLUETOOTH_VOLUME", + "WAKE_LOCK", + "MONITOR_LOCATION", + "MONITOR_HIGH_POWER_LOCATION", + "GET_USAGE_STATS", + "MUTE_MICROPHONE", + "TOAST_WINDOW", + "PROJECT_MEDIA", + "ACTIVATE_VPN", + "WRITE_WALLPAPER", + "ASSIST_STRUCTURE", + "ASSIST_SCREENSHOT", + "READ_PHONE_STATE", + "ADD_VOICEMAIL", + "USE_SIP", + "PROCESS_OUTGOING_CALLS", + "USE_FINGERPRINT", + "BODY_SENSORS", + "READ_CELL_BROADCASTS", + "MOCK_LOCATION", + "READ_EXTERNAL_STORAGE", + "WRITE_EXTERNAL_STORAGE", + "TURN_ON_SCREEN", + "GET_ACCOUNTS", + "RUN_IN_BACKGROUND", + "AUDIO_ACCESSIBILITY_VOLUME", + "READ_PHONE_NUMBERS", + "REQUEST_INSTALL_PACKAGES", + "PICTURE_IN_PICTURE", + "INSTANT_APP_START_FOREGROUND", + "ANSWER_PHONE_CALLS", + "RUN_ANY_IN_BACKGROUND", + "CHANGE_WIFI_STATE", + "REQUEST_DELETE_PACKAGES", + "BIND_ACCESSIBILITY_SERVICE", + "ACCEPT_HANDOVER", + "MANAGE_IPSEC_TUNNELS", + "START_FOREGROUND", + "BLUETOOTH_SCAN", + "USE_BIOMETRIC", + "ACTIVITY_RECOGNITION", + "SMS_FINANCIAL_TRANSACTIONS", + "READ_MEDIA_AUDIO", + "WRITE_MEDIA_AUDIO", + "READ_MEDIA_VIDEO", + "WRITE_MEDIA_VIDEO", + "READ_MEDIA_IMAGES", + "WRITE_MEDIA_IMAGES", + "LEGACY_STORAGE", + "ACCESS_ACCESSIBILITY", + "READ_DEVICE_IDENTIFIERS", + }; + + /** + * This optionally maps a permission to an operation. If there + * is no permission associated with an operation, it is null. + */ + @UnsupportedAppUsage + private static String[] sOpPerms = new String[] { + android.Manifest.permission.ACCESS_COARSE_LOCATION, + android.Manifest.permission.ACCESS_FINE_LOCATION, + null, + android.Manifest.permission.VIBRATE, + android.Manifest.permission.READ_CONTACTS, + android.Manifest.permission.WRITE_CONTACTS, + android.Manifest.permission.READ_CALL_LOG, + android.Manifest.permission.WRITE_CALL_LOG, + android.Manifest.permission.READ_CALENDAR, + android.Manifest.permission.WRITE_CALENDAR, + android.Manifest.permission.ACCESS_WIFI_STATE, + null, // no permission required for notifications + null, // neighboring cells shares the coarse location perm + android.Manifest.permission.CALL_PHONE, + android.Manifest.permission.READ_SMS, + null, // no permission required for writing sms + android.Manifest.permission.RECEIVE_SMS, + android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST, + android.Manifest.permission.RECEIVE_MMS, + android.Manifest.permission.RECEIVE_WAP_PUSH, + android.Manifest.permission.SEND_SMS, + android.Manifest.permission.READ_SMS, + null, // no permission required for writing icc sms + android.Manifest.permission.WRITE_SETTINGS, + android.Manifest.permission.SYSTEM_ALERT_WINDOW, + android.Manifest.permission.ACCESS_NOTIFICATIONS, + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO, + null, // no permission for playing audio + null, // no permission for reading clipboard + null, // no permission for writing clipboard + null, // no permission for taking media buttons + null, // no permission for taking audio focus + null, // no permission for changing master volume + null, // no permission for changing voice volume + null, // no permission for changing ring volume + null, // no permission for changing media volume + null, // no permission for changing alarm volume + null, // no permission for changing notification volume + null, // no permission for changing bluetooth volume + android.Manifest.permission.WAKE_LOCK, + null, // no permission for generic location monitoring + null, // no permission for high power location monitoring + android.Manifest.permission.PACKAGE_USAGE_STATS, + null, // no permission for muting/unmuting microphone + null, // no permission for displaying toasts + null, // no permission for projecting media + null, // no permission for activating vpn + null, // no permission for supporting wallpaper + null, // no permission for receiving assist structure + null, // no permission for receiving assist screenshot + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ADD_VOICEMAIL, + Manifest.permission.USE_SIP, + Manifest.permission.PROCESS_OUTGOING_CALLS, + Manifest.permission.USE_FINGERPRINT, + Manifest.permission.BODY_SENSORS, + Manifest.permission.READ_CELL_BROADCASTS, + null, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + null, // no permission for turning the screen on + Manifest.permission.GET_ACCOUNTS, + null, // no permission for running in background + null, // no permission for changing accessibility volume + Manifest.permission.READ_PHONE_NUMBERS, + Manifest.permission.REQUEST_INSTALL_PACKAGES, + null, // no permission for entering picture-in-picture on hide + Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, + Manifest.permission.ANSWER_PHONE_CALLS, + null, // no permission for OP_RUN_ANY_IN_BACKGROUND + Manifest.permission.CHANGE_WIFI_STATE, + Manifest.permission.REQUEST_DELETE_PACKAGES, + Manifest.permission.BIND_ACCESSIBILITY_SERVICE, + Manifest.permission.ACCEPT_HANDOVER, + null, // no permission for OP_MANAGE_IPSEC_TUNNELS + Manifest.permission.FOREGROUND_SERVICE, + null, // no permission for OP_BLUETOOTH_SCAN + Manifest.permission.USE_BIOMETRIC, + Manifest.permission.ACTIVITY_RECOGNITION, + Manifest.permission.SMS_FINANCIAL_TRANSACTIONS, + null, + null, // no permission for OP_WRITE_MEDIA_AUDIO + null, + null, // no permission for OP_WRITE_MEDIA_VIDEO + null, + null, // no permission for OP_WRITE_MEDIA_IMAGES + null, // no permission for OP_LEGACY_STORAGE + null, // no permission for OP_ACCESS_ACCESSIBILITY + null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS + }; + + /** + * Specifies whether an Op should be restricted by a user restriction. + * Each Op should be filled with a restriction string from UserManager or + * null to specify it is not affected by any user restriction. + */ + private static String[] sOpRestrictions = new String[] { + UserManager.DISALLOW_SHARE_LOCATION, //COARSE_LOCATION + UserManager.DISALLOW_SHARE_LOCATION, //FINE_LOCATION + UserManager.DISALLOW_SHARE_LOCATION, //GPS + null, //VIBRATE + null, //READ_CONTACTS + null, //WRITE_CONTACTS + UserManager.DISALLOW_OUTGOING_CALLS, //READ_CALL_LOG + UserManager.DISALLOW_OUTGOING_CALLS, //WRITE_CALL_LOG + null, //READ_CALENDAR + null, //WRITE_CALENDAR + UserManager.DISALLOW_SHARE_LOCATION, //WIFI_SCAN + null, //POST_NOTIFICATION + null, //NEIGHBORING_CELLS + null, //CALL_PHONE + UserManager.DISALLOW_SMS, //READ_SMS + UserManager.DISALLOW_SMS, //WRITE_SMS + UserManager.DISALLOW_SMS, //RECEIVE_SMS + null, //RECEIVE_EMERGENCY_SMS + UserManager.DISALLOW_SMS, //RECEIVE_MMS + null, //RECEIVE_WAP_PUSH + UserManager.DISALLOW_SMS, //SEND_SMS + UserManager.DISALLOW_SMS, //READ_ICC_SMS + UserManager.DISALLOW_SMS, //WRITE_ICC_SMS + null, //WRITE_SETTINGS + UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW + null, //ACCESS_NOTIFICATIONS + UserManager.DISALLOW_CAMERA, //CAMERA + UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO + null, //PLAY_AUDIO + null, //READ_CLIPBOARD + null, //WRITE_CLIPBOARD + null, //TAKE_MEDIA_BUTTONS + null, //TAKE_AUDIO_FOCUS + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MASTER_VOLUME + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_VOICE_VOLUME + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_RING_VOLUME + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MEDIA_VOLUME + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ALARM_VOLUME + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_NOTIFICATION_VOLUME + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_BLUETOOTH_VOLUME + null, //WAKE_LOCK + UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_LOCATION + UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_HIGH_POWER_LOCATION + null, //GET_USAGE_STATS + UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE + UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW + null, //PROJECT_MEDIA + null, // ACTIVATE_VPN + UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER + null, // ASSIST_STRUCTURE + null, // ASSIST_SCREENSHOT + null, // READ_PHONE_STATE + null, // ADD_VOICEMAIL + null, // USE_SIP + null, // PROCESS_OUTGOING_CALLS + null, // USE_FINGERPRINT + null, // BODY_SENSORS + null, // READ_CELL_BROADCASTS + null, // MOCK_LOCATION + null, // READ_EXTERNAL_STORAGE + null, // WRITE_EXTERNAL_STORAGE + null, // TURN_ON_SCREEN + null, // GET_ACCOUNTS + null, // RUN_IN_BACKGROUND + UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME + null, // READ_PHONE_NUMBERS + null, // REQUEST_INSTALL_PACKAGES + null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE + null, // INSTANT_APP_START_FOREGROUND + null, // ANSWER_PHONE_CALLS + null, // OP_RUN_ANY_IN_BACKGROUND + null, // OP_CHANGE_WIFI_STATE + null, // REQUEST_DELETE_PACKAGES + null, // OP_BIND_ACCESSIBILITY_SERVICE + null, // ACCEPT_HANDOVER + null, // MANAGE_IPSEC_TUNNELS + null, // START_FOREGROUND + null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN + null, // USE_BIOMETRIC + null, // ACTIVITY_RECOGNITION + UserManager.DISALLOW_SMS, // SMS_FINANCIAL_TRANSACTIONS + null, // READ_MEDIA_AUDIO + null, // WRITE_MEDIA_AUDIO + null, // READ_MEDIA_VIDEO + null, // WRITE_MEDIA_VIDEO + null, // READ_MEDIA_IMAGES + null, // WRITE_MEDIA_IMAGES + null, // LEGACY_STORAGE + null, // ACCESS_ACCESSIBILITY + null, // READ_DEVICE_IDENTIFIERS + }; + + /** + * This specifies whether each option should allow the system + * (and system ui) to bypass the user restriction when active. + */ + private static boolean[] sOpAllowSystemRestrictionBypass = new boolean[] { + true, //COARSE_LOCATION + true, //FINE_LOCATION + false, //GPS + false, //VIBRATE + false, //READ_CONTACTS + false, //WRITE_CONTACTS + false, //READ_CALL_LOG + false, //WRITE_CALL_LOG + false, //READ_CALENDAR + false, //WRITE_CALENDAR + true, //WIFI_SCAN + false, //POST_NOTIFICATION + false, //NEIGHBORING_CELLS + false, //CALL_PHONE + false, //READ_SMS + false, //WRITE_SMS + false, //RECEIVE_SMS + false, //RECEIVE_EMERGECY_SMS + false, //RECEIVE_MMS + false, //RECEIVE_WAP_PUSH + false, //SEND_SMS + false, //READ_ICC_SMS + false, //WRITE_ICC_SMS + false, //WRITE_SETTINGS + true, //SYSTEM_ALERT_WINDOW + false, //ACCESS_NOTIFICATIONS + false, //CAMERA + false, //RECORD_AUDIO + false, //PLAY_AUDIO + false, //READ_CLIPBOARD + false, //WRITE_CLIPBOARD + false, //TAKE_MEDIA_BUTTONS + false, //TAKE_AUDIO_FOCUS + false, //AUDIO_MASTER_VOLUME + false, //AUDIO_VOICE_VOLUME + false, //AUDIO_RING_VOLUME + false, //AUDIO_MEDIA_VOLUME + false, //AUDIO_ALARM_VOLUME + false, //AUDIO_NOTIFICATION_VOLUME + false, //AUDIO_BLUETOOTH_VOLUME + false, //WAKE_LOCK + false, //MONITOR_LOCATION + false, //MONITOR_HIGH_POWER_LOCATION + false, //GET_USAGE_STATS + false, //MUTE_MICROPHONE + true, //TOAST_WINDOW + false, //PROJECT_MEDIA + false, //ACTIVATE_VPN + false, //WALLPAPER + false, //ASSIST_STRUCTURE + false, //ASSIST_SCREENSHOT + false, //READ_PHONE_STATE + false, //ADD_VOICEMAIL + false, // USE_SIP + false, // PROCESS_OUTGOING_CALLS + false, // USE_FINGERPRINT + false, // BODY_SENSORS + false, // READ_CELL_BROADCASTS + false, // MOCK_LOCATION + false, // READ_EXTERNAL_STORAGE + false, // WRITE_EXTERNAL_STORAGE + false, // TURN_ON_SCREEN + false, // GET_ACCOUNTS + false, // RUN_IN_BACKGROUND + false, // AUDIO_ACCESSIBILITY_VOLUME + false, // READ_PHONE_NUMBERS + false, // REQUEST_INSTALL_PACKAGES + false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE + false, // INSTANT_APP_START_FOREGROUND + false, // ANSWER_PHONE_CALLS + false, // OP_RUN_ANY_IN_BACKGROUND + false, // OP_CHANGE_WIFI_STATE + false, // OP_REQUEST_DELETE_PACKAGES + false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER + false, // MANAGE_IPSEC_HANDOVERS + false, // START_FOREGROUND + true, // BLUETOOTH_SCAN + false, // USE_BIOMETRIC + false, // ACTIVITY_RECOGNITION + false, // SMS_FINANCIAL_TRANSACTIONS + false, // READ_MEDIA_AUDIO + false, // WRITE_MEDIA_AUDIO + false, // READ_MEDIA_VIDEO + false, // WRITE_MEDIA_VIDEO + false, // READ_MEDIA_IMAGES + false, // WRITE_MEDIA_IMAGES + false, // LEGACY_STORAGE + false, // ACCESS_ACCESSIBILITY + false, // READ_DEVICE_IDENTIFIERS + }; + + /** + * This specifies the default mode for each operation. + */ + private static int[] sOpDefaultMode = new int[] { + AppOpsManager.MODE_ALLOWED, // COARSE_LOCATION + AppOpsManager.MODE_ALLOWED, // FINE_LOCATION + AppOpsManager.MODE_ALLOWED, // GPS + AppOpsManager.MODE_ALLOWED, // VIBRATE + AppOpsManager.MODE_ALLOWED, // READ_CONTACTS + AppOpsManager.MODE_ALLOWED, // WRITE_CONTACTS + AppOpsManager.MODE_ALLOWED, // READ_CALL_LOG + AppOpsManager.MODE_ALLOWED, // WRITE_CALL_LOG + AppOpsManager.MODE_ALLOWED, // READ_CALENDAR + AppOpsManager.MODE_ALLOWED, // WRITE_CALENDAR + AppOpsManager.MODE_ALLOWED, // WIFI_SCAN + AppOpsManager.MODE_ALLOWED, // POST_NOTIFICATION + AppOpsManager.MODE_ALLOWED, // NEIGHBORING_CELLS + AppOpsManager.MODE_ALLOWED, // CALL_PHONE + AppOpsManager.MODE_ALLOWED, // READ_SMS + AppOpsManager.MODE_IGNORED, // WRITE_SMS + AppOpsManager.MODE_ALLOWED, // RECEIVE_SMS + AppOpsManager.MODE_ALLOWED, // RECEIVE_EMERGENCY_BROADCAST + AppOpsManager.MODE_ALLOWED, // RECEIVE_MMS + AppOpsManager.MODE_ALLOWED, // RECEIVE_WAP_PUSH + AppOpsManager.MODE_ALLOWED, // SEND_SMS + AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS + AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS + AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS + getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW + AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS + AppOpsManager.MODE_ALLOWED, // CAMERA + AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO + AppOpsManager.MODE_ALLOWED, // PLAY_AUDIO + AppOpsManager.MODE_ALLOWED, // READ_CLIPBOARD + AppOpsManager.MODE_ALLOWED, // WRITE_CLIPBOARD + AppOpsManager.MODE_ALLOWED, // TAKE_MEDIA_BUTTONS + AppOpsManager.MODE_ALLOWED, // TAKE_AUDIO_FOCUS + AppOpsManager.MODE_ALLOWED, // AUDIO_MASTER_VOLUME + AppOpsManager.MODE_ALLOWED, // AUDIO_VOICE_VOLUME + AppOpsManager.MODE_ALLOWED, // AUDIO_RING_VOLUME + AppOpsManager.MODE_ALLOWED, // AUDIO_MEDIA_VOLUME + AppOpsManager.MODE_ALLOWED, // AUDIO_ALARM_VOLUME + AppOpsManager.MODE_ALLOWED, // AUDIO_NOTIFICATION_VOLUME + AppOpsManager.MODE_ALLOWED, // AUDIO_BLUETOOTH_VOLUME + AppOpsManager.MODE_ALLOWED, // WAKE_LOCK + AppOpsManager.MODE_ALLOWED, // MONITOR_LOCATION + AppOpsManager.MODE_ALLOWED, // MONITOR_HIGH_POWER_LOCATION + AppOpsManager.MODE_DEFAULT, // GET_USAGE_STATS + AppOpsManager.MODE_ALLOWED, // MUTE_MICROPHONE + AppOpsManager.MODE_ALLOWED, // TOAST_WINDOW + AppOpsManager.MODE_IGNORED, // PROJECT_MEDIA + AppOpsManager.MODE_IGNORED, // ACTIVATE_VPN + AppOpsManager.MODE_ALLOWED, // WRITE_WALLPAPER + AppOpsManager.MODE_ALLOWED, // ASSIST_STRUCTURE + AppOpsManager.MODE_ALLOWED, // ASSIST_SCREENSHOT + AppOpsManager.MODE_ALLOWED, // READ_PHONE_STATE + AppOpsManager.MODE_ALLOWED, // ADD_VOICEMAIL + AppOpsManager.MODE_ALLOWED, // USE_SIP + AppOpsManager.MODE_ALLOWED, // PROCESS_OUTGOING_CALLS + AppOpsManager.MODE_ALLOWED, // USE_FINGERPRINT + AppOpsManager.MODE_ALLOWED, // BODY_SENSORS + AppOpsManager.MODE_ALLOWED, // READ_CELL_BROADCASTS + AppOpsManager.MODE_ERRORED, // MOCK_LOCATION + AppOpsManager.MODE_ALLOWED, // READ_EXTERNAL_STORAGE + AppOpsManager.MODE_ALLOWED, // WRITE_EXTERNAL_STORAGE + AppOpsManager.MODE_ALLOWED, // TURN_SCREEN_ON + AppOpsManager.MODE_ALLOWED, // GET_ACCOUNTS + AppOpsManager.MODE_ALLOWED, // RUN_IN_BACKGROUND + AppOpsManager.MODE_ALLOWED, // AUDIO_ACCESSIBILITY_VOLUME + AppOpsManager.MODE_ALLOWED, // READ_PHONE_NUMBERS + AppOpsManager.MODE_DEFAULT, // REQUEST_INSTALL_PACKAGES + AppOpsManager.MODE_ALLOWED, // PICTURE_IN_PICTURE + AppOpsManager.MODE_DEFAULT, // INSTANT_APP_START_FOREGROUND + AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS + AppOpsManager.MODE_ALLOWED, // RUN_ANY_IN_BACKGROUND + AppOpsManager.MODE_ALLOWED, // CHANGE_WIFI_STATE + AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES + AppOpsManager.MODE_ALLOWED, // BIND_ACCESSIBILITY_SERVICE + AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER + AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS + AppOpsManager.MODE_ALLOWED, // START_FOREGROUND + AppOpsManager.MODE_ALLOWED, // BLUETOOTH_SCAN + AppOpsManager.MODE_ALLOWED, // USE_BIOMETRIC + AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION + AppOpsManager.MODE_DEFAULT, // SMS_FINANCIAL_TRANSACTIONS + AppOpsManager.MODE_ALLOWED, // READ_MEDIA_AUDIO + AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_AUDIO + AppOpsManager.MODE_ALLOWED, // READ_MEDIA_VIDEO + AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_VIDEO + AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES + AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES + AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE + AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY + AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS + }; + + /** + * This specifies whether each option is allowed to be reset + * when resetting all app preferences. Disable reset for + * app ops that are under strong control of some part of the + * system (such as OP_WRITE_SMS, which should be allowed only + * for whichever app is selected as the current SMS app). + */ + private static boolean[] sOpDisableReset = new boolean[] { + false, // COARSE_LOCATION + false, // FINE_LOCATION + false, // GPS + false, // VIBRATE + false, // READ_CONTACTS + false, // WRITE_CONTACTS + false, // READ_CALL_LOG + false, // WRITE_CALL_LOG + false, // READ_CALENDAR + false, // WRITE_CALENDAR + false, // WIFI_SCAN + false, // POST_NOTIFICATION + false, // NEIGHBORING_CELLS + false, // CALL_PHONE + true, // READ_SMS + true, // WRITE_SMS + true, // RECEIVE_SMS + false, // RECEIVE_EMERGENCY_BROADCAST + false, // RECEIVE_MMS + true, // RECEIVE_WAP_PUSH + true, // SEND_SMS + false, // READ_ICC_SMS + false, // WRITE_ICC_SMS + false, // WRITE_SETTINGS + false, // SYSTEM_ALERT_WINDOW + false, // ACCESS_NOTIFICATIONS + false, // CAMERA + false, // RECORD_AUDIO + false, // PLAY_AUDIO + false, // READ_CLIPBOARD + false, // WRITE_CLIPBOARD + false, // TAKE_MEDIA_BUTTONS + false, // TAKE_AUDIO_FOCUS + false, // AUDIO_MASTER_VOLUME + false, // AUDIO_VOICE_VOLUME + false, // AUDIO_RING_VOLUME + false, // AUDIO_MEDIA_VOLUME + false, // AUDIO_ALARM_VOLUME + false, // AUDIO_NOTIFICATION_VOLUME + false, // AUDIO_BLUETOOTH_VOLUME + false, // WAKE_LOCK + false, // MONITOR_LOCATION + false, // MONITOR_HIGH_POWER_LOCATION + false, // GET_USAGE_STATS + false, // MUTE_MICROPHONE + false, // TOAST_WINDOW + false, // PROJECT_MEDIA + false, // ACTIVATE_VPN + false, // WRITE_WALLPAPER + false, // ASSIST_STRUCTURE + false, // ASSIST_SCREENSHOT + false, // READ_PHONE_STATE + false, // ADD_VOICEMAIL + false, // USE_SIP + false, // PROCESS_OUTGOING_CALLS + false, // USE_FINGERPRINT + false, // BODY_SENSORS + true, // READ_CELL_BROADCASTS + false, // MOCK_LOCATION + false, // READ_EXTERNAL_STORAGE + false, // WRITE_EXTERNAL_STORAGE + false, // TURN_SCREEN_ON + false, // GET_ACCOUNTS + false, // RUN_IN_BACKGROUND + false, // AUDIO_ACCESSIBILITY_VOLUME + false, // READ_PHONE_NUMBERS + false, // REQUEST_INSTALL_PACKAGES + false, // PICTURE_IN_PICTURE + false, // INSTANT_APP_START_FOREGROUND + false, // ANSWER_PHONE_CALLS + false, // RUN_ANY_IN_BACKGROUND + false, // CHANGE_WIFI_STATE + false, // REQUEST_DELETE_PACKAGES + false, // BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER + false, // MANAGE_IPSEC_TUNNELS + false, // START_FOREGROUND + false, // BLUETOOTH_SCAN + false, // USE_BIOMETRIC + false, // ACTIVITY_RECOGNITION + false, // SMS_FINANCIAL_TRANSACTIONS + false, // READ_MEDIA_AUDIO + false, // WRITE_MEDIA_AUDIO + false, // READ_MEDIA_VIDEO + false, // WRITE_MEDIA_VIDEO + false, // READ_MEDIA_IMAGES + false, // WRITE_MEDIA_IMAGES + false, // LEGACY_STORAGE + false, // ACCESS_ACCESSIBILITY + false, // READ_DEVICE_IDENTIFIERS + }; + + /** + * Mapping from an app op name to the app op code. + */ + private static HashMap<String, Integer> sOpStrToOp = new HashMap<>(); + + /** + * Mapping from a permission to the corresponding app op. + */ + private static HashMap<String, Integer> sPermToOp = new HashMap<>(); + + static { + if (sOpToSwitch.length != _NUM_OP) { + throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length + + " should be " + _NUM_OP); + } + if (sOpToString.length != _NUM_OP) { + throw new IllegalStateException("sOpToString length " + sOpToString.length + + " should be " + _NUM_OP); + } + if (sOpNames.length != _NUM_OP) { + throw new IllegalStateException("sOpNames length " + sOpNames.length + + " should be " + _NUM_OP); + } + if (sOpPerms.length != _NUM_OP) { + throw new IllegalStateException("sOpPerms length " + sOpPerms.length + + " should be " + _NUM_OP); + } + if (sOpDefaultMode.length != _NUM_OP) { + throw new IllegalStateException("sOpDefaultMode length " + sOpDefaultMode.length + + " should be " + _NUM_OP); + } + if (sOpDisableReset.length != _NUM_OP) { + throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length + + " should be " + _NUM_OP); + } + if (sOpRestrictions.length != _NUM_OP) { + throw new IllegalStateException("sOpRestrictions length " + sOpRestrictions.length + + " should be " + _NUM_OP); + } + if (sOpAllowSystemRestrictionBypass.length != _NUM_OP) { + throw new IllegalStateException("sOpAllowSYstemRestrictionsBypass length " + + sOpRestrictions.length + " should be " + _NUM_OP); + } + for (int i=0; i<_NUM_OP; i++) { + if (sOpToString[i] != null) { + sOpStrToOp.put(sOpToString[i], i); + } + } + for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) { + if (sOpPerms[op] != null) { + sPermToOp.put(sOpPerms[op], op); + } + } + } + + /** @hide */ + public static final String KEY_HISTORICAL_OPS = "historical_ops"; + + /** System properties for debug logging of noteOp call sites */ + private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled"; + private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages"; + private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops"; + private static final String DEBUG_LOGGING_TAG = "AppOpsManager"; + + /** + * Retrieve the op switch that controls the given operation. + * @hide + */ + @UnsupportedAppUsage + public static int opToSwitch(int op) { + return sOpToSwitch[op]; + } + + /** + * Retrieve a non-localized name for the operation, for debugging output. + * @hide + */ + @UnsupportedAppUsage + public static String opToName(int op) { + if (op == OP_NONE) return "NONE"; + return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")"); + } + + /** + * Retrieve a non-localized public name for the operation. + * + * @hide + */ + public static @NonNull String opToPublicName(int op) { + return sOpToString[op]; + } + + /** + * @hide + */ + public static int strDebugOpToOp(String op) { + for (int i=0; i<sOpNames.length; i++) { + if (sOpNames[i].equals(op)) { + return i; + } + } + throw new IllegalArgumentException("Unknown operation string: " + op); + } + + /** + * Retrieve the permission associated with an operation, or null if there is not one. + * @hide + */ + @TestApi + public static String opToPermission(int op) { + return sOpPerms[op]; + } + + /** + * Retrieve the permission associated with an operation, or null if there is not one. + * + * @param op The operation name. + * + * @hide + */ + @Nullable + @SystemApi + public static String opToPermission(@NonNull String op) { + return opToPermission(strOpToOp(op)); + } + + /** + * Retrieve the user restriction associated with an operation, or null if there is not one. + * @hide + */ + public static String opToRestriction(int op) { + return sOpRestrictions[op]; + } + + /** + * Retrieve the app op code for a permission, or null if there is not one. + * This API is intended to be used for mapping runtime or appop permissions + * to the corresponding app op. + * @hide + */ + @TestApi + public static int permissionToOpCode(String permission) { + Integer boxedOpCode = sPermToOp.get(permission); + return boxedOpCode != null ? boxedOpCode : OP_NONE; + } + + /** + * Retrieve whether the op allows the system (and system ui) to + * bypass the user restriction. + * @hide + */ + public static boolean opAllowSystemBypassRestriction(int op) { + return sOpAllowSystemRestrictionBypass[op]; + } + + /** + * Retrieve the default mode for the operation. + * @hide + */ + public static @Mode int opToDefaultMode(int op) { + return sOpDefaultMode[op]; + } + + /** + * Retrieve the default mode for the app op. + * + * @param appOp The app op name + * + * @return the default mode for the app op + * + * @hide + */ + @TestApi + @SystemApi + public static int opToDefaultMode(@NonNull String appOp) { + return opToDefaultMode(strOpToOp(appOp)); + } + + /** + * Retrieve the human readable mode. + * @hide + */ + public static String modeToName(@Mode int mode) { + if (mode >= 0 && mode < MODE_NAMES.length) { + return MODE_NAMES[mode]; + } + return "mode=" + mode; + } + + /** + * Retrieve whether the op allows itself to be reset. + * @hide + */ + public static boolean opAllowsReset(int op) { + return !sOpDisableReset[op]; + } + + /** + * Class holding all of the operation information associated with an app. + * @hide + */ + @SystemApi + public static final class PackageOps implements Parcelable { + private final String mPackageName; + private final int mUid; + private final List<OpEntry> mEntries; + + /** + * @hide + */ + @UnsupportedAppUsage + public PackageOps(String packageName, int uid, List<OpEntry> entries) { + mPackageName = packageName; + mUid = uid; + mEntries = entries; + } + + /** + * @return The name of the package. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * @return The uid of the package. + */ + public int getUid() { + return mUid; + } + + /** + * @return The ops of the package. + */ + public @NonNull List<OpEntry> getOps() { + return mEntries; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeInt(mUid); + dest.writeInt(mEntries.size()); + for (int i=0; i<mEntries.size(); i++) { + mEntries.get(i).writeToParcel(dest, flags); + } + } + + PackageOps(Parcel source) { + mPackageName = source.readString(); + mUid = source.readInt(); + mEntries = new ArrayList<OpEntry>(); + final int N = source.readInt(); + for (int i=0; i<N; i++) { + mEntries.add(OpEntry.CREATOR.createFromParcel(source)); + } + } + + public static final @android.annotation.NonNull Creator<PackageOps> CREATOR = new Creator<PackageOps>() { + @Override public PackageOps createFromParcel(Parcel source) { + return new PackageOps(source); + } + + @Override public PackageOps[] newArray(int size) { + return new PackageOps[size]; + } + }; + } + + /** + * Class holding the information about one unique operation of an application. + * @hide + */ + @TestApi + @Immutable + @SystemApi + public static final class OpEntry implements Parcelable { + private final int mOp; + private final boolean mRunning; + private final @Mode int mMode; + private final @Nullable LongSparseLongArray mAccessTimes; + private final @Nullable LongSparseLongArray mRejectTimes; + private final @Nullable LongSparseLongArray mDurations; + private final @Nullable LongSparseLongArray mProxyUids; + private final @Nullable LongSparseArray<String> mProxyPackageNames; + + /** + * @hide + */ + public OpEntry(int op, boolean running, @Mode int mode, + @Nullable LongSparseLongArray accessTimes, @Nullable LongSparseLongArray rejectTimes, + @Nullable LongSparseLongArray durations, @Nullable LongSparseLongArray proxyUids, + @Nullable LongSparseArray<String> proxyPackageNames) { + mOp = op; + mRunning = running; + mMode = mode; + mAccessTimes = accessTimes; + mRejectTimes = rejectTimes; + mDurations = durations; + mProxyUids = proxyUids; + mProxyPackageNames = proxyPackageNames; + } + + /** + * @hide + */ + public OpEntry(int op, @Mode int mode) { + mOp = op; + mMode = mode; + mRunning = false; + mAccessTimes = null; + mRejectTimes = null; + mDurations = null; + mProxyUids = null; + mProxyPackageNames = null; + } + + /** + * Returns all keys for which we have mapped state in any of the data buckets - + * access time, reject time, duration. + * @hide */ + public @Nullable LongSparseArray<Object> collectKeys() { + LongSparseArray<Object> result = AppOpsManager.collectKeys(mAccessTimes, null); + result = AppOpsManager.collectKeys(mRejectTimes, result); + result = AppOpsManager.collectKeys(mDurations, result); + return result; + } + + /** + * @hide + */ + @UnsupportedAppUsage + public int getOp() { + return mOp; + } + + /** + * @return This entry's op string name, such as {@link #OPSTR_COARSE_LOCATION}. + */ + public @NonNull String getOpStr() { + return sOpToString[mOp]; + } + + /** + * @return this entry's current mode, such as {@link #MODE_ALLOWED}. + */ + public @Mode int getMode() { + return mMode; + } + + /** + * @hide + */ + @UnsupportedAppUsage + public long getTime() { + return getLastAccessTime(OP_FLAGS_ALL); + } + + /** + * Return the last wall clock time in milliseconds this op was accessed. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last access time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastAccessForegroundTime(int) + * @see #getLastAccessBackgroundTime(int) + * @see #getLastAccessTime(int, int, int) + */ + public long getLastAccessTime(@OpFlags int flags) { + return maxForFlagsInStates(mAccessTimes, MAX_PRIORITY_UID_STATE, + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Return the last wall clock time in milliseconds this op was accessed + * by the app while in the foreground. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last foreground access time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastAccessBackgroundTime(int) + * @see #getLastAccessTime(int) + * @see #getLastAccessTime(int, int, int) + */ + public long getLastAccessForegroundTime(@OpFlags int flags) { + return maxForFlagsInStates(mAccessTimes, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** + * Return the last wall clock time in milliseconds this op was accessed + * by the app while in the background. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last foreground access time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastAccessForegroundTime(int) + * @see #getLastAccessTime(int) + * @see #getLastAccessTime(int, int, int) + */ + public long getLastAccessBackgroundTime(@OpFlags int flags) { + return maxForFlagsInStates(mAccessTimes, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Return the last wall clock time in milliseconds this op was accessed + * by the app for a given range of UID states. + * + * @param fromUidState The UID state for which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param toUidState The UID state for which to query. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * + * @return the last foreground access time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastAccessForegroundTime(int) + * @see #getLastAccessBackgroundTime(int) + * @see #getLastAccessTime(int) + */ + public long getLastAccessTime(@UidState int fromUidState, @UidState int toUidState, + @OpFlags int flags) { + return maxForFlagsInStates(mAccessTimes, fromUidState, toUidState, flags); + } + + /** + * @hide + */ + @UnsupportedAppUsage + public long getRejectTime() { + return getLastRejectTime(OP_FLAGS_ALL); + } + + /** + * Return the last wall clock time in milliseconds the app made an attempt + * to access this op but was rejected. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last reject time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastRejectBackgroundTime(int) + * @see #getLastRejectForegroundTime(int) + * @see #getLastRejectTime(int, int, int) + */ + public long getLastRejectTime(@OpFlags int flags) { + return maxForFlagsInStates(mRejectTimes, MAX_PRIORITY_UID_STATE, + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Return the last wall clock time in milliseconds the app made an attempt + * to access this op while in the foreground but was rejected. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last foreground reject time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastRejectBackgroundTime(int) + * @see #getLastRejectTime(int, int, int) + * @see #getLastRejectTime(int) + */ + public long getLastRejectForegroundTime(@OpFlags int flags) { + return maxForFlagsInStates(mRejectTimes, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** + * Return the last wall clock time in milliseconds the app made an attempt + * to access this op while in the background but was rejected. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last background reject time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastRejectForegroundTime(int) + * @see #getLastRejectTime(int, int, int) + * @see #getLastRejectTime(int) + */ + public long getLastRejectBackgroundTime(@OpFlags int flags) { + return maxForFlagsInStates(mRejectTimes, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Return the last wall clock time state in milliseconds the app made an + * attempt to access this op for a given range of UID states. + * + * @param fromUidState The UID state from which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param toUidState The UID state to which to query. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the last foreground access time in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + * + * @see #getLastRejectForegroundTime(int) + * @see #getLastRejectBackgroundTime(int) + * @see #getLastRejectTime(int) + */ + public long getLastRejectTime(@UidState int fromUidState, @UidState int toUidState, + @OpFlags int flags) { + return maxForFlagsInStates(mRejectTimes, fromUidState, toUidState, flags); + } + + /** + * @return Whether the operation is running. + */ + public boolean isRunning() { + return mRunning; + } + + /** + * @return The duration of the operation in milliseconds. The duration is in wall time. + */ + public long getDuration() { + return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL); + } + + /** + * Return the duration in milliseconds the app accessed this op while + * in the foreground. The duration is in wall time. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the foreground access duration in milliseconds. + * + * @see #getLastBackgroundDuration(int) + * @see #getLastDuration(int, int, int) + */ + public long getLastForegroundDuration(@OpFlags int flags) { + return sumForFlagsInStates(mDurations, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** + * Return the duration in milliseconds the app accessed this op while + * in the background. The duration is in wall time. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the background access duration in milliseconds. + * + * @see #getLastForegroundDuration(int) + * @see #getLastDuration(int, int, int) + */ + public long getLastBackgroundDuration(@OpFlags int flags) { + return sumForFlagsInStates(mDurations, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Return the duration in milliseconds the app accessed this op for + * a given range of UID states. The duration is in wall time. + * + * @param fromUidState The UID state for which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param toUidState The UID state for which to query. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return the access duration in milliseconds. + */ + public long getLastDuration(@UidState int fromUidState, @UidState int toUidState, + @OpFlags int flags) { + return sumForFlagsInStates(mDurations, fromUidState, toUidState, flags); + } + + /** + * Gets the UID of the app that performed the op on behalf of this app and + * as a result blamed the op on this app or {@link Process#INVALID_UID} if + * there is no proxy. + * + * @return The proxy UID. + */ + public int getProxyUid() { + return (int) findFirstNonNegativeForFlagsInStates(mDurations, + MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL); + } + + /** + * Gets the UID of the app that performed the op on behalf of this app and + * as a result blamed the op on this app or {@link Process#INVALID_UID} if + * there is no proxy. + * + * @param uidState The UID state for which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * + * @return The proxy UID. + */ + public int getProxyUid(@UidState int uidState, @OpFlags int flags) { + return (int) findFirstNonNegativeForFlagsInStates(mDurations, + uidState, uidState, flags); + } + + /** + * Gets the package name of the app that performed the op on behalf of this + * app and as a result blamed the op on this app or {@code null} + * if there is no proxy. + * + * @return The proxy package name. + */ + public @Nullable String getProxyPackageName() { + return findFirstNonNullForFlagsInStates(mProxyPackageNames, MAX_PRIORITY_UID_STATE, + MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL); + } + + /** + * Gets the package name of the app that performed the op on behalf of this + * app and as a result blamed the op on this app for a UID state or + * {@code null} if there is no proxy. + * + * @param uidState The UID state for which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The proxy package name. + */ + public @Nullable String getProxyPackageName(@UidState int uidState, @OpFlags int flags) { + return findFirstNonNullForFlagsInStates(mProxyPackageNames, uidState, uidState, flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mOp); + dest.writeInt(mMode); + dest.writeBoolean(mRunning); + writeLongSparseLongArrayToParcel(mAccessTimes, dest); + writeLongSparseLongArrayToParcel(mRejectTimes, dest); + writeLongSparseLongArrayToParcel(mDurations, dest); + writeLongSparseLongArrayToParcel(mProxyUids, dest); + writeLongSparseStringArrayToParcel(mProxyPackageNames, dest); + } + + OpEntry(Parcel source) { + mOp = source.readInt(); + mMode = source.readInt(); + mRunning = source.readBoolean(); + mAccessTimes = readLongSparseLongArrayFromParcel(source); + mRejectTimes = readLongSparseLongArrayFromParcel(source); + mDurations = readLongSparseLongArrayFromParcel(source); + mProxyUids = readLongSparseLongArrayFromParcel(source); + mProxyPackageNames = readLongSparseStringArrayFromParcel(source); + } + + public static final @android.annotation.NonNull Creator<OpEntry> CREATOR = new Creator<OpEntry>() { + @Override public OpEntry createFromParcel(Parcel source) { + return new OpEntry(source); + } + + @Override public OpEntry[] newArray(int size) { + return new OpEntry[size]; + } + }; + } + + /** @hide */ + public interface HistoricalOpsVisitor { + void visitHistoricalOps(@NonNull HistoricalOps ops); + void visitHistoricalUidOps(@NonNull HistoricalUidOps ops); + void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops); + void visitHistoricalOp(@NonNull HistoricalOp ops); + } + + /** + * Request for getting historical app op usage. The request acts + * as a filtering criteria when querying historical op usage. + * + * @hide + */ + @Immutable + @TestApi + @SystemApi + public static final class HistoricalOpsRequest { + private final int mUid; + private final @Nullable String mPackageName; + private final @Nullable List<String> mOpNames; + private final long mBeginTimeMillis; + private final long mEndTimeMillis; + private final @OpFlags int mFlags; + + private HistoricalOpsRequest(int uid, @Nullable String packageName, + @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis, + @OpFlags int flags) { + mUid = uid; + mPackageName = packageName; + mOpNames = opNames; + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + mFlags = flags; + } + + /** + * Builder for creating a {@link HistoricalOpsRequest}. + * + * @hide + */ + @TestApi + @SystemApi + public static final class Builder { + private int mUid = Process.INVALID_UID; + private @Nullable String mPackageName; + private @Nullable List<String> mOpNames; + private final long mBeginTimeMillis; + private final long mEndTimeMillis; + private @OpFlags int mFlags = OP_FLAGS_ALL; + + /** + * Creates a new builder. + * + * @param beginTimeMillis The beginning of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non + * negative. + * @param endTimeMillis The end of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after + * {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent + * history including ops that happen while this call is in flight. + */ + public Builder(long beginTimeMillis, long endTimeMillis) { + Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis, + "beginTimeMillis must be non negative and lesser than endTimeMillis"); + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** + * Sets the UID to query for. + * + * @param uid The uid. Pass {@link android.os.Process#INVALID_UID} for any uid. + * @return This builder. + */ + public @NonNull Builder setUid(int uid) { + Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, + "uid must be " + Process.INVALID_UID + " or non negative"); + mUid = uid; + return this; + } + + /** + * Sets the package to query for. + * + * @param packageName The package name. <code>Null</code> for any package. + * @return This builder. + */ + public @NonNull Builder setPackageName(@Nullable String packageName) { + mPackageName = packageName; + return this; + } + + /** + * Sets the op names to query for. + * + * @param opNames The op names. <code>Null</code> for any op. + * @return This builder. + */ + public @NonNull Builder setOpNames(@Nullable List<String> opNames) { + if (opNames != null) { + final int opCount = opNames.size(); + for (int i = 0; i < opCount; i++) { + Preconditions.checkArgument(AppOpsManager.strOpToOp( + opNames.get(i)) != AppOpsManager.OP_NONE); + } + } + mOpNames = opNames; + return this; + } + + /** + * Sets the op flags to query for. The flags specify the type of + * op data being queried. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return This builder. + */ + public @NonNull Builder setFlags(@OpFlags int flags) { + Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL); + mFlags = flags; + return this; + } + + /** + * @return a new {@link HistoricalOpsRequest}. + */ + public @NonNull HistoricalOpsRequest build() { + return new HistoricalOpsRequest(mUid, mPackageName, mOpNames, + mBeginTimeMillis, mEndTimeMillis, mFlags); + } + } + } + + /** + * This class represents historical app op state of all UIDs for a given time interval. + * + * @hide + */ + @TestApi + @SystemApi + public static final class HistoricalOps implements Parcelable { + private long mBeginTimeMillis; + private long mEndTimeMillis; + private @Nullable SparseArray<HistoricalUidOps> mHistoricalUidOps; + + /** @hide */ + @TestApi + public HistoricalOps(long beginTimeMillis, long endTimeMillis) { + Preconditions.checkState(beginTimeMillis <= endTimeMillis); + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** @hide */ + public HistoricalOps(@NonNull HistoricalOps other) { + mBeginTimeMillis = other.mBeginTimeMillis; + mEndTimeMillis = other.mEndTimeMillis; + Preconditions.checkState(mBeginTimeMillis <= mEndTimeMillis); + if (other.mHistoricalUidOps != null) { + final int opCount = other.getUidCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalUidOps origOps = other.getUidOpsAt(i); + final HistoricalUidOps clonedOps = new HistoricalUidOps(origOps); + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(opCount); + } + mHistoricalUidOps.put(clonedOps.getUid(), clonedOps); + } + } + } + + private HistoricalOps(Parcel parcel) { + mBeginTimeMillis = parcel.readLong(); + mEndTimeMillis = parcel.readLong(); + final int[] uids = parcel.createIntArray(); + if (!ArrayUtils.isEmpty(uids)) { + final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable( + HistoricalOps.class.getClassLoader()); + final List<HistoricalUidOps> uidOps = (listSlice != null) + ? listSlice.getList() : null; + if (uidOps == null) { + return; + } + for (int i = 0; i < uids.length; i++) { + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(); + } + mHistoricalUidOps.put(uids[i], uidOps.get(i)); + } + } + } + + /** + * Splice a piece from the beginning of these ops. + * + * @param splicePoint The fraction of the data to be spliced off. + * + * @hide + */ + public @NonNull HistoricalOps spliceFromBeginning(double splicePoint) { + return splice(splicePoint, true); + } + + /** + * Splice a piece from the end of these ops. + * + * @param fractionToRemove The fraction of the data to be spliced off. + * + * @hide + */ + public @NonNull HistoricalOps spliceFromEnd(double fractionToRemove) { + return splice(fractionToRemove, false); + } + + /** + * Splice a piece from the beginning or end of these ops. + * + * @param fractionToRemove The fraction of the data to be spliced off. + * @param beginning Whether to splice off the beginning or the end. + * + * @return The spliced off part. + * + * @hide + */ + private @Nullable HistoricalOps splice(double fractionToRemove, boolean beginning) { + final long spliceBeginTimeMills; + final long spliceEndTimeMills; + if (beginning) { + spliceBeginTimeMills = mBeginTimeMillis; + spliceEndTimeMills = (long) (mBeginTimeMillis + + getDurationMillis() * fractionToRemove); + mBeginTimeMillis = spliceEndTimeMills; + } else { + spliceBeginTimeMills = (long) (mEndTimeMillis + - getDurationMillis() * fractionToRemove); + spliceEndTimeMills = mEndTimeMillis; + mEndTimeMillis = spliceBeginTimeMills; + } + + HistoricalOps splice = null; + final int uidCount = getUidCount(); + for (int i = 0; i < uidCount; i++) { + final HistoricalUidOps origOps = getUidOpsAt(i); + final HistoricalUidOps spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalOps(spliceBeginTimeMills, spliceEndTimeMills); + } + if (splice.mHistoricalUidOps == null) { + splice.mHistoricalUidOps = new SparseArray<>(); + } + splice.mHistoricalUidOps.put(spliceOps.getUid(), spliceOps); + } + } + return splice; + } + + /** + * Merge the passed ops into the current ones. The time interval is a + * union of the current and passed in one and the passed in data is + * folded into the data of this instance. + * + * @hide + */ + public void merge(@NonNull HistoricalOps other) { + mBeginTimeMillis = Math.min(mBeginTimeMillis, other.mBeginTimeMillis); + mEndTimeMillis = Math.max(mEndTimeMillis, other.mEndTimeMillis); + final int uidCount = other.getUidCount(); + for (int i = 0; i < uidCount; i++) { + final HistoricalUidOps otherUidOps = other.getUidOpsAt(i); + final HistoricalUidOps thisUidOps = getUidOps(otherUidOps.getUid()); + if (thisUidOps != null) { + thisUidOps.merge(otherUidOps); + } else { + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(); + } + mHistoricalUidOps.put(otherUidOps.getUid(), otherUidOps); + } + } + } + + /** + * AppPermissionUsage the ops to leave only the data we filter for. + * + * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all. + * @param packageName Package to filter for or null for all. + * @param opNames Ops to filter for or null for all. + * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all. + * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all. + * + * @hide + */ + public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames, + long beginTimeMillis, long endTimeMillis) { + final long durationMillis = getDurationMillis(); + mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis); + mEndTimeMillis = Math.min(mEndTimeMillis, endTimeMillis); + final double scaleFactor = Math.min((double) (endTimeMillis - beginTimeMillis) + / (double) durationMillis, 1); + final int uidCount = getUidCount(); + for (int i = uidCount - 1; i >= 0; i--) { + final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i); + if (uid != Process.INVALID_UID && uid != uidOp.getUid()) { + mHistoricalUidOps.removeAt(i); + } else { + uidOp.filter(packageName, opNames, scaleFactor); + } + } + } + + /** @hide */ + public boolean isEmpty() { + if (getBeginTimeMillis() >= getEndTimeMillis()) { + return true; + } + final int uidCount = getUidCount(); + for (int i = uidCount - 1; i >= 0; i--) { + final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i); + if (!uidOp.isEmpty()) { + return false; + } + } + return true; + } + + /** @hide */ + public long getDurationMillis() { + return mEndTimeMillis - mBeginTimeMillis; + } + + /** @hide */ + @TestApi + public void increaseAccessCount(int opCode, int uid, @NonNull String packageName, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode, + packageName, uidState, flags, increment); + } + + /** @hide */ + @TestApi + public void increaseRejectCount(int opCode, int uid, @NonNull String packageName, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode, + packageName, uidState, flags, increment); + } + + /** @hide */ + @TestApi + public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode, + packageName, uidState, flags, increment); + } + + /** @hide */ + @TestApi + public void offsetBeginAndEndTime(long offsetMillis) { + mBeginTimeMillis += offsetMillis; + mEndTimeMillis += offsetMillis; + } + + /** @hide */ + public void setBeginAndEndTime(long beginTimeMillis, long endTimeMillis) { + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** @hide */ + public void setBeginTime(long beginTimeMillis) { + mBeginTimeMillis = beginTimeMillis; + } + + /** @hide */ + public void setEndTime(long endTimeMillis) { + mEndTimeMillis = endTimeMillis; + } + + /** + * @return The beginning of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + */ + public long getBeginTimeMillis() { + return mBeginTimeMillis; + } + + /** + * @return The end of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + */ + public long getEndTimeMillis() { + return mEndTimeMillis; + } + + /** + * Gets number of UIDs with historical ops. + * + * @return The number of UIDs with historical ops. + * + * @see #getUidOpsAt(int) + */ + public @IntRange(from = 0) int getUidCount() { + if (mHistoricalUidOps == null) { + return 0; + } + return mHistoricalUidOps.size(); + } + + /** + * Gets the historical UID ops at a given index. + * + * @param index The index. + * + * @return The historical UID ops at the given index. + * + * @see #getUidCount() + */ + public @NonNull HistoricalUidOps getUidOpsAt(@IntRange(from = 0) int index) { + if (mHistoricalUidOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalUidOps.valueAt(index); + } + + /** + * Gets the historical UID ops for a given UID. + * + * @param uid The UID. + * + * @return The historical ops for the UID. + */ + public @Nullable HistoricalUidOps getUidOps(int uid) { + if (mHistoricalUidOps == null) { + return null; + } + return mHistoricalUidOps.get(uid); + } + + /** @hide */ + public void clearHistory(int uid, @NonNull String packageName) { + HistoricalUidOps historicalUidOps = getOrCreateHistoricalUidOps(uid); + historicalUidOps.clearHistory(packageName); + if (historicalUidOps.isEmpty()) { + mHistoricalUidOps.remove(uid); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeLong(mBeginTimeMillis); + parcel.writeLong(mEndTimeMillis); + if (mHistoricalUidOps != null) { + final int uidCount = mHistoricalUidOps.size(); + parcel.writeInt(uidCount); + for (int i = 0; i < uidCount; i++) { + parcel.writeInt(mHistoricalUidOps.keyAt(i)); + } + final List<HistoricalUidOps> opsList = new ArrayList<>(uidCount); + for (int i = 0; i < uidCount; i++) { + opsList.add(mHistoricalUidOps.valueAt(i)); + } + parcel.writeParcelable(new ParceledListSlice<>(opsList), flags); + } else { + parcel.writeInt(-1); + } + } + + /** + * Accepts a visitor to traverse the ops tree. + * + * @param visitor The visitor. + * + * @hide + */ + public void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalOps(this); + final int uidCount = getUidCount(); + for (int i = 0; i < uidCount; i++) { + getUidOpsAt(i).accept(visitor); + } + } + + private @NonNull HistoricalUidOps getOrCreateHistoricalUidOps(int uid) { + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(); + } + HistoricalUidOps historicalUidOp = mHistoricalUidOps.get(uid); + if (historicalUidOp == null) { + historicalUidOp = new HistoricalUidOps(uid); + mHistoricalUidOps.put(uid, historicalUidOp); + } + return historicalUidOp; + } + + /** + * @return Rounded value up at the 0.5 boundary. + * + * @hide + */ + public static double round(double value) { + final BigDecimal decimalScale = new BigDecimal(value); + return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalOps other = (HistoricalOps) obj; + if (mBeginTimeMillis != other.mBeginTimeMillis) { + return false; + } + if (mEndTimeMillis != other.mEndTimeMillis) { + return false; + } + if (mHistoricalUidOps == null) { + if (other.mHistoricalUidOps != null) { + return false; + } + } else if (!mHistoricalUidOps.equals(other.mHistoricalUidOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = (int) (mBeginTimeMillis ^ (mBeginTimeMillis >>> 32)); + result = 31 * result + mHistoricalUidOps.hashCode(); + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[from:" + + mBeginTimeMillis + " to:" + mEndTimeMillis + "]"; + } + + public static final @android.annotation.NonNull Creator<HistoricalOps> CREATOR = new Creator<HistoricalOps>() { + @Override + public @NonNull HistoricalOps createFromParcel(@NonNull Parcel parcel) { + return new HistoricalOps(parcel); + } + + @Override + public @NonNull HistoricalOps[] newArray(int size) { + return new HistoricalOps[size]; + } + }; + } + + /** + * This class represents historical app op state for a UID. + * + * @hide + */ + @TestApi + @SystemApi + public static final class HistoricalUidOps implements Parcelable { + private final int mUid; + private @Nullable ArrayMap<String, HistoricalPackageOps> mHistoricalPackageOps; + + /** @hide */ + public HistoricalUidOps(int uid) { + mUid = uid; + } + + private HistoricalUidOps(@NonNull HistoricalUidOps other) { + mUid = other.mUid; + final int opCount = other.getPackageCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalPackageOps origOps = other.getPackageOpsAt(i); + final HistoricalPackageOps cloneOps = new HistoricalPackageOps(origOps); + if (mHistoricalPackageOps == null) { + mHistoricalPackageOps = new ArrayMap<>(opCount); + } + mHistoricalPackageOps.put(cloneOps.getPackageName(), cloneOps); + } + } + + private HistoricalUidOps(@NonNull Parcel parcel) { + // No arg check since we always read from a trusted source. + mUid = parcel.readInt(); + mHistoricalPackageOps = parcel.createTypedArrayMap(HistoricalPackageOps.CREATOR); + } + + private @Nullable HistoricalUidOps splice(double fractionToRemove) { + HistoricalUidOps splice = null; + final int packageCount = getPackageCount(); + for (int i = 0; i < packageCount; i++) { + final HistoricalPackageOps origOps = getPackageOpsAt(i); + final HistoricalPackageOps spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalUidOps(mUid); + } + if (splice.mHistoricalPackageOps == null) { + splice.mHistoricalPackageOps = new ArrayMap<>(); + } + splice.mHistoricalPackageOps.put(spliceOps.getPackageName(), spliceOps); + } + } + return splice; + } + + private void merge(@NonNull HistoricalUidOps other) { + final int packageCount = other.getPackageCount(); + for (int i = 0; i < packageCount; i++) { + final HistoricalPackageOps otherPackageOps = other.getPackageOpsAt(i); + final HistoricalPackageOps thisPackageOps = getPackageOps( + otherPackageOps.getPackageName()); + if (thisPackageOps != null) { + thisPackageOps.merge(otherPackageOps); + } else { + if (mHistoricalPackageOps == null) { + mHistoricalPackageOps = new ArrayMap<>(); + } + mHistoricalPackageOps.put(otherPackageOps.getPackageName(), otherPackageOps); + } + } + } + + private void filter(@Nullable String packageName, @Nullable String[] opNames, + double fractionToRemove) { + final int packageCount = getPackageCount(); + for (int i = packageCount - 1; i >= 0; i--) { + final HistoricalPackageOps packageOps = getPackageOpsAt(i); + if (packageName != null && !packageName.equals(packageOps.getPackageName())) { + mHistoricalPackageOps.removeAt(i); + } else { + packageOps.filter(opNames, fractionToRemove); + } + } + } + + private boolean isEmpty() { + final int packageCount = getPackageCount(); + for (int i = packageCount - 1; i >= 0; i--) { + final HistoricalPackageOps packageOps = mHistoricalPackageOps.valueAt(i); + if (!packageOps.isEmpty()) { + return false; + } + } + return true; + } + + private void increaseAccessCount(int opCode, @NonNull String packageName, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalPackageOps(packageName).increaseAccessCount( + opCode, uidState, flags, increment); + } + + private void increaseRejectCount(int opCode, @NonNull String packageName, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalPackageOps(packageName).increaseRejectCount( + opCode, uidState, flags, increment); + } + + private void increaseAccessDuration(int opCode, @NonNull String packageName, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration( + opCode, uidState, flags, increment); + } + + /** + * @return The UID for which the data is related. + */ + public int getUid() { + return mUid; + } + + /** + * Gets number of packages with historical ops. + * + * @return The number of packages with historical ops. + * + * @see #getPackageOpsAt(int) + */ + public @IntRange(from = 0) int getPackageCount() { + if (mHistoricalPackageOps == null) { + return 0; + } + return mHistoricalPackageOps.size(); + } + + /** + * Gets the historical package ops at a given index. + * + * @param index The index. + * + * @return The historical package ops at the given index. + * + * @see #getPackageCount() + */ + public @NonNull HistoricalPackageOps getPackageOpsAt(@IntRange(from = 0) int index) { + if (mHistoricalPackageOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalPackageOps.valueAt(index); + } + + /** + * Gets the historical package ops for a given package. + * + * @param packageName The package. + * + * @return The historical ops for the package. + */ + public @Nullable HistoricalPackageOps getPackageOps(@NonNull String packageName) { + if (mHistoricalPackageOps == null) { + return null; + } + return mHistoricalPackageOps.get(packageName); + } + + private void clearHistory(@NonNull String packageName) { + if (mHistoricalPackageOps != null) { + mHistoricalPackageOps.remove(packageName); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mUid); + parcel.writeTypedArrayMap(mHistoricalPackageOps, flags); + } + + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalUidOps(this); + final int packageCount = getPackageCount(); + for (int i = 0; i < packageCount; i++) { + getPackageOpsAt(i).accept(visitor); + } + } + + private @NonNull HistoricalPackageOps getOrCreateHistoricalPackageOps( + @NonNull String packageName) { + if (mHistoricalPackageOps == null) { + mHistoricalPackageOps = new ArrayMap<>(); + } + HistoricalPackageOps historicalPackageOp = mHistoricalPackageOps.get(packageName); + if (historicalPackageOp == null) { + historicalPackageOp = new HistoricalPackageOps(packageName); + mHistoricalPackageOps.put(packageName, historicalPackageOp); + } + return historicalPackageOp; + } + + + public static final @android.annotation.NonNull Creator<HistoricalUidOps> CREATOR = new Creator<HistoricalUidOps>() { + @Override + public @NonNull HistoricalUidOps createFromParcel(@NonNull Parcel parcel) { + return new HistoricalUidOps(parcel); + } + + @Override + public @NonNull HistoricalUidOps[] newArray(int size) { + return new HistoricalUidOps[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalUidOps other = (HistoricalUidOps) obj; + if (mUid != other.mUid) { + return false; + } + if (mHistoricalPackageOps == null) { + if (other.mHistoricalPackageOps != null) { + return false; + } + } else if (!mHistoricalPackageOps.equals(other.mHistoricalPackageOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = mUid; + result = 31 * result + (mHistoricalPackageOps != null + ? mHistoricalPackageOps.hashCode() : 0); + return result; + } + } + + /** + * This class represents historical app op information about a package. + * + * @hide + */ + @TestApi + @SystemApi + public static final class HistoricalPackageOps implements Parcelable { + private final @NonNull String mPackageName; + private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps; + + /** @hide */ + public HistoricalPackageOps(@NonNull String packageName) { + mPackageName = packageName; + } + + private HistoricalPackageOps(@NonNull HistoricalPackageOps other) { + mPackageName = other.mPackageName; + final int opCount = other.getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp origOp = other.getOpAt(i); + final HistoricalOp cloneOp = new HistoricalOp(origOp); + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(opCount); + } + mHistoricalOps.put(cloneOp.getOpName(), cloneOp); + } + } + + private HistoricalPackageOps(@NonNull Parcel parcel) { + mPackageName = parcel.readString(); + mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR); + } + + private @Nullable HistoricalPackageOps splice(double fractionToRemove) { + HistoricalPackageOps splice = null; + final int opCount = getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp origOps = getOpAt(i); + final HistoricalOp spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalPackageOps(mPackageName); + } + if (splice.mHistoricalOps == null) { + splice.mHistoricalOps = new ArrayMap<>(); + } + splice.mHistoricalOps.put(spliceOps.getOpName(), spliceOps); + } + } + return splice; + } + + private void merge(@NonNull HistoricalPackageOps other) { + final int opCount = other.getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp otherOp = other.getOpAt(i); + final HistoricalOp thisOp = getOp(otherOp.getOpName()); + if (thisOp != null) { + thisOp.merge(otherOp); + } else { + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(); + } + mHistoricalOps.put(otherOp.getOpName(), otherOp); + } + } + } + + private void filter(@Nullable String[] opNames, double scaleFactor) { + final int opCount = getOpCount(); + for (int i = opCount - 1; i >= 0; i--) { + final HistoricalOp op = mHistoricalOps.valueAt(i); + if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) { + mHistoricalOps.removeAt(i); + } else { + op.filter(scaleFactor); + } + } + } + + private boolean isEmpty() { + final int opCount = getOpCount(); + for (int i = opCount - 1; i >= 0; i--) { + final HistoricalOp op = mHistoricalOps.valueAt(i); + if (!op.isEmpty()) { + return false; + } + } + return true; + } + + private void increaseAccessCount(int opCode, @UidState int uidState, + @OpFlags int flags, long increment) { + getOrCreateHistoricalOp(opCode).increaseAccessCount(uidState, flags, increment); + } + + private void increaseRejectCount(int opCode, @UidState int uidState, + @OpFlags int flags, long increment) { + getOrCreateHistoricalOp(opCode).increaseRejectCount(uidState, flags, increment); + } + + private void increaseAccessDuration(int opCode, @UidState int uidState, + @OpFlags int flags, long increment) { + getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, flags, increment); + } + + /** + * Gets the package name which the data represents. + * + * @return The package name which the data represents. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * Gets number historical app ops. + * + * @return The number historical app ops. + * @see #getOpAt(int) + */ + public @IntRange(from = 0) int getOpCount() { + if (mHistoricalOps == null) { + return 0; + } + return mHistoricalOps.size(); + } + + /** + * Gets the historical op at a given index. + * + * @param index The index to lookup. + * @return The op at the given index. + * @see #getOpCount() + */ + public @NonNull HistoricalOp getOpAt(@IntRange(from = 0) int index) { + if (mHistoricalOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalOps.valueAt(index); + } + + /** + * Gets the historical entry for a given op name. + * + * @param opName The op name. + * @return The historical entry for that op name. + */ + public @Nullable HistoricalOp getOp(@NonNull String opName) { + if (mHistoricalOps == null) { + return null; + } + return mHistoricalOps.get(opName); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeString(mPackageName); + parcel.writeTypedArrayMap(mHistoricalOps, flags); + } + + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalPackageOps(this); + final int opCount = getOpCount(); + for (int i = 0; i < opCount; i++) { + getOpAt(i).accept(visitor); + } + } + + private @NonNull HistoricalOp getOrCreateHistoricalOp(int opCode) { + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(); + } + final String opStr = sOpToString[opCode]; + HistoricalOp op = mHistoricalOps.get(opStr); + if (op == null) { + op = new HistoricalOp(opCode); + mHistoricalOps.put(opStr, op); + } + return op; + } + + public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR = + new Creator<HistoricalPackageOps>() { + @Override + public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) { + return new HistoricalPackageOps(parcel); + } + + @Override + public @NonNull HistoricalPackageOps[] newArray(int size) { + return new HistoricalPackageOps[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalPackageOps other = (HistoricalPackageOps) obj; + if (!mPackageName.equals(other.mPackageName)) { + return false; + } + if (mHistoricalOps == null) { + if (other.mHistoricalOps != null) { + return false; + } + } else if (!mHistoricalOps.equals(other.mHistoricalOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = mPackageName != null ? mPackageName.hashCode() : 0; + result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0); + return result; + } + } + + /** + * This class represents historical information about an app op. + * + * @hide + */ + @TestApi + @SystemApi + public static final class HistoricalOp implements Parcelable { + private final int mOp; + private @Nullable LongSparseLongArray mAccessCount; + private @Nullable LongSparseLongArray mRejectCount; + private @Nullable LongSparseLongArray mAccessDuration; + + /** @hide */ + public HistoricalOp(int op) { + mOp = op; + } + + private HistoricalOp(@NonNull HistoricalOp other) { + mOp = other.mOp; + if (other.mAccessCount != null) { + mAccessCount = other.mAccessCount.clone(); + } + if (other.mRejectCount != null) { + mRejectCount = other.mRejectCount.clone(); + } + if (other.mAccessDuration != null) { + mAccessDuration = other.mAccessDuration.clone(); + } + } + + private HistoricalOp(@NonNull Parcel parcel) { + mOp = parcel.readInt(); + mAccessCount = readLongSparseLongArrayFromParcel(parcel); + mRejectCount = readLongSparseLongArrayFromParcel(parcel); + mAccessDuration = readLongSparseLongArrayFromParcel(parcel); + } + + private void filter(double scaleFactor) { + scale(mAccessCount, scaleFactor); + scale(mRejectCount, scaleFactor); + scale(mAccessDuration, scaleFactor); + } + + private boolean isEmpty() { + return !hasData(mAccessCount) + && !hasData(mRejectCount) + && !hasData(mAccessDuration); + } + + private boolean hasData(@NonNull LongSparseLongArray array) { + return (array != null && array.size() > 0); + } + + private @Nullable HistoricalOp splice(double fractionToRemove) { + final HistoricalOp splice = new HistoricalOp(mOp); + splice(mAccessCount, splice::getOrCreateAccessCount, fractionToRemove); + splice(mRejectCount, splice::getOrCreateRejectCount, fractionToRemove); + splice(mAccessDuration, splice::getOrCreateAccessDuration, fractionToRemove); + return splice; + } + + private static void splice(@Nullable LongSparseLongArray sourceContainer, + @NonNull Supplier<LongSparseLongArray> destContainerProvider, + double fractionToRemove) { + if (sourceContainer != null) { + final int size = sourceContainer.size(); + for (int i = 0; i < size; i++) { + final long key = sourceContainer.keyAt(i); + final long value = sourceContainer.valueAt(i); + final long removedFraction = Math.round(value * fractionToRemove); + if (removedFraction > 0) { + destContainerProvider.get().put(key, removedFraction); + sourceContainer.put(key, value - removedFraction); + } + } + } + } + + private void merge(@NonNull HistoricalOp other) { + merge(this::getOrCreateAccessCount, other.mAccessCount); + merge(this::getOrCreateRejectCount, other.mRejectCount); + merge(this::getOrCreateAccessDuration, other.mAccessDuration); + } + + private void increaseAccessCount(@UidState int uidState, @OpFlags int flags, + long increment) { + increaseCount(getOrCreateAccessCount(), uidState, flags, increment); + } + + private void increaseRejectCount(@UidState int uidState, @OpFlags int flags, + long increment) { + increaseCount(getOrCreateRejectCount(), uidState, flags, increment); + } + + private void increaseAccessDuration(@UidState int uidState, @OpFlags int flags, + long increment) { + increaseCount(getOrCreateAccessDuration(), uidState, flags, increment); + } + + private void increaseCount(@NonNull LongSparseLongArray counts, + @UidState int uidState, @OpFlags int flags, long increment) { + while (flags != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(flags); + flags &= ~flag; + final long key = makeKey(uidState, flag); + counts.put(key, counts.get(key) + increment); + } + } + + /** + * Gets the op name. + * + * @return The op name. + */ + public @NonNull String getOpName() { + return sOpToString[mOp]; + } + + /** @hide */ + public int getOpCode() { + return mOp; + } + + /** + * Gets the number times the op was accessed (performed) in the foreground. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The times the op was accessed in the foreground. + * + * @see #getBackgroundAccessCount(int) + * @see #getAccessCount(int, int, int) + */ + public long getForegroundAccessCount(@OpFlags int flags) { + return sumForFlagsInStates(mAccessCount, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** + * Gets the number times the op was accessed (performed) in the background. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The times the op was accessed in the background. + * + * @see #getForegroundAccessCount(int) + * @see #getAccessCount(int, int, int) + */ + public long getBackgroundAccessCount(@OpFlags int flags) { + return sumForFlagsInStates(mAccessCount, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Gets the number times the op was accessed (performed) for a + * range of uid states. + * + * @param fromUidState The UID state from which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE_LOCATION}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param toUidState The UID state to which to query. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * + * @return The times the op was accessed for the given UID state. + * + * @see #getForegroundAccessCount(int) + * @see #getBackgroundAccessCount(int) + */ + public long getAccessCount(@UidState int fromUidState, @UidState int toUidState, + @OpFlags int flags) { + return sumForFlagsInStates(mAccessCount, fromUidState, toUidState, flags); + } + + /** + * Gets the number times the op was rejected in the foreground. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The times the op was rejected in the foreground. + * + * @see #getBackgroundRejectCount(int) + * @see #getRejectCount(int, int, int) + */ + public long getForegroundRejectCount(@OpFlags int flags) { + return sumForFlagsInStates(mRejectCount, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** + * Gets the number times the op was rejected in the background. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The times the op was rejected in the background. + * + * @see #getForegroundRejectCount(int) + * @see #getRejectCount(int, int, int) + */ + public long getBackgroundRejectCount(@OpFlags int flags) { + return sumForFlagsInStates(mRejectCount, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Gets the number times the op was rejected for a given range of UID states. + * + * @param fromUidState The UID state from which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE_LOCATION}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param toUidState The UID state to which to query. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * + * @return The times the op was rejected for the given UID state. + * + * @see #getForegroundRejectCount(int) + * @see #getBackgroundRejectCount(int) + */ + public long getRejectCount(@UidState int fromUidState, @UidState int toUidState, + @OpFlags int flags) { + return sumForFlagsInStates(mRejectCount, fromUidState, toUidState, flags); + } + + /** + * Gets the total duration the app op was accessed (performed) in the foreground. + * The duration is in wall time. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The total duration the app op was accessed in the foreground. + * + * @see #getBackgroundAccessDuration(int) + * @see #getAccessDuration(int, int, int) + */ + public long getForegroundAccessDuration(@OpFlags int flags) { + return sumForFlagsInStates(mAccessDuration, MAX_PRIORITY_UID_STATE, + resolveFirstUnrestrictedUidState(mOp), flags); + } + + /** + * Gets the total duration the app op was accessed (performed) in the background. + * The duration is in wall time. + * + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * @return The total duration the app op was accessed in the background. + * + * @see #getForegroundAccessDuration(int) + * @see #getAccessDuration(int, int, int) + */ + public long getBackgroundAccessDuration(@OpFlags int flags) { + return sumForFlagsInStates(mAccessDuration, resolveLastRestrictedUidState(mOp), + MIN_PRIORITY_UID_STATE, flags); + } + + /** + * Gets the total duration the app op was accessed (performed) for a given + * range of UID states. The duration is in wall time. + * + * @param fromUidState The UID state from which to query. Could be one of + * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP}, + * {@link #UID_STATE_FOREGROUND_SERVICE_LOCATION}, + * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND}, + * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}. + * @param toUidState The UID state from which to query. + * @param flags The flags which are any combination of + * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY}, + * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED}, + * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL} + * for any flag. + * + * @return The total duration the app op was accessed for the given UID state. + * + * @see #getForegroundAccessDuration(int) + * @see #getBackgroundAccessDuration(int) + */ + public long getAccessDuration(@UidState int fromUidState, @UidState int toUidState, + @OpFlags int flags) { + return sumForFlagsInStates(mAccessDuration, fromUidState, toUidState, flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mOp); + writeLongSparseLongArrayToParcel(mAccessCount, parcel); + writeLongSparseLongArrayToParcel(mRejectCount, parcel); + writeLongSparseLongArrayToParcel(mAccessDuration, parcel); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalOp other = (HistoricalOp) obj; + if (mOp != other.mOp) { + return false; + } + if (!Objects.equals(mAccessCount, other.mAccessCount)) { + return false; + } + if (!Objects.equals(mRejectCount, other.mRejectCount)) { + return false; + } + return Objects.equals(mAccessDuration, other.mAccessDuration); + } + + @Override + public int hashCode() { + int result = mOp; + result = 31 * result + Objects.hashCode(mAccessCount); + result = 31 * result + Objects.hashCode(mRejectCount); + result = 31 * result + Objects.hashCode(mAccessDuration); + return result; + } + + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalOp(this); + } + + private @NonNull LongSparseLongArray getOrCreateAccessCount() { + if (mAccessCount == null) { + mAccessCount = new LongSparseLongArray(); + } + return mAccessCount; + } + + private @NonNull LongSparseLongArray getOrCreateRejectCount() { + if (mRejectCount == null) { + mRejectCount = new LongSparseLongArray(); + } + return mRejectCount; + } + + private @NonNull LongSparseLongArray getOrCreateAccessDuration() { + if (mAccessDuration == null) { + mAccessDuration = new LongSparseLongArray(); + } + return mAccessDuration; + } + + /** + * Multiplies the entries in the array with the passed in scale factor and + * rounds the result at up 0.5 boundary. + * + * @param data The data to scale. + * @param scaleFactor The scale factor. + */ + private static void scale(@NonNull LongSparseLongArray data, double scaleFactor) { + if (data != null) { + final int size = data.size(); + for (int i = 0; i < size; i++) { + data.put(data.keyAt(i), (long) HistoricalOps.round( + (double) data.valueAt(i) * scaleFactor)); + } + } + } + + /** + * Merges two arrays while lazily acquiring the destination. + * + * @param thisSupplier The destination supplier. + * @param other The array to merge in. + */ + private static void merge(@NonNull Supplier<LongSparseLongArray> thisSupplier, + @Nullable LongSparseLongArray other) { + if (other != null) { + final int otherSize = other.size(); + for (int i = 0; i < otherSize; i++) { + final LongSparseLongArray that = thisSupplier.get(); + final long otherKey = other.keyAt(i); + final long otherValue = other.valueAt(i); + that.put(otherKey, that.get(otherKey) + otherValue); + } + } + } + + /** @hide */ + public @Nullable LongSparseArray<Object> collectKeys() { + LongSparseArray<Object> result = AppOpsManager.collectKeys(mAccessCount, + null /*result*/); + result = AppOpsManager.collectKeys(mRejectCount, result); + result = AppOpsManager.collectKeys(mAccessDuration, result); + return result; + } + + public static final @android.annotation.NonNull Creator<HistoricalOp> CREATOR = + new Creator<HistoricalOp>() { + @Override + public @NonNull HistoricalOp createFromParcel(@NonNull Parcel source) { + return new HistoricalOp(source); + } + + @Override + public @NonNull HistoricalOp[] newArray(int size) { + return new HistoricalOp[size]; + } + }; + } + + /** + * Computes the sum of the counts for the given flags in between the begin and + * end UID states. + * + * @param counts The data array. + * @param beginUidState The beginning UID state (exclusive). + * @param endUidState The end UID state. + * @param flags The UID flags. + * @return The sum. + */ + private static long sumForFlagsInStates(@Nullable LongSparseLongArray counts, + @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) { + if (counts == null) { + return 0; + } + long sum = 0; + while (flags != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(flags); + flags &= ~flag; + for (int uidState : UID_STATES) { + if (uidState < beginUidState || uidState > endUidState) { + continue; + } + final long key = makeKey(uidState, flag); + sum += counts.get(key); + } + } + return sum; + } + + /** + * Finds the first non-negative value for the given flags in between the begin and + * end UID states. + * + * @param counts The data array. + * @param flags The UID flags. + * @param beginUidState The beginning UID state (exclusive). + * @param endUidState The end UID state. + * @return The non-negative value or -1. + */ + private static long findFirstNonNegativeForFlagsInStates(@Nullable LongSparseLongArray counts, + @OpFlags int flags, @UidState int beginUidState, @UidState int endUidState) { + if (counts == null) { + return -1; + } + while (flags != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(flags); + flags &= ~flag; + for (int uidState : UID_STATES) { + if (uidState < beginUidState || uidState > endUidState) { + continue; + } + final long key = makeKey(uidState, flag); + final long value = counts.get(key); + if (value >= 0) { + return value; + } + } + } + return -1; + } + + /** + * Finds the first non-null value for the given flags in between the begin and + * end UID states. + * + * @param counts The data array. + * @param flags The UID flags. + * @param beginUidState The beginning UID state (exclusive). + * @param endUidState The end UID state. + * @return The non-negative value or -1. + */ + private static @Nullable String findFirstNonNullForFlagsInStates( + @Nullable LongSparseArray<String> counts, @OpFlags int flags, + @UidState int beginUidState, @UidState int endUidState) { + if (counts == null) { + return null; + } + while (flags != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(flags); + flags &= ~flag; + for (int uidState : UID_STATES) { + if (uidState < beginUidState || uidState > endUidState) { + continue; + } + final long key = makeKey(uidState, flag); + final String value = counts.get(key); + if (value != null) { + return value; + } + } + } + return null; + } + + /** + * Callback for notification of changes to operation state. + */ + public interface OnOpChangedListener { + public void onOpChanged(String op, String packageName); + } + + /** + * Callback for notification of changes to operation active state. + * + * @hide + */ + @TestApi + public interface OnOpActiveChangedListener { + /** + * Called when the active state of an app op changes. + * + * @param code The op code. + * @param uid The UID performing the operation. + * @param packageName The package performing the operation. + * @param active Whether the operation became active or inactive. + */ + void onOpActiveChanged(int code, int uid, String packageName, boolean active); + } + + /** + * Callback for notification of an op being noted. + * + * @hide + */ + public interface OnOpNotedListener { + /** + * Called when an op was noted. + * + * @param code The op code. + * @param uid The UID performing the operation. + * @param packageName The package performing the operation. + * @param result The result of the note. + */ + void onOpNoted(int code, int uid, String packageName, int result); + } + + /** + * Callback for notification of changes to operation state. + * This allows you to see the raw op codes instead of strings. + * @hide + */ + public static class OnOpChangedInternalListener implements OnOpChangedListener { + public void onOpChanged(String op, String packageName) { } + public void onOpChanged(int op, String packageName) { } + } + + AppOpsManager(Context context, IAppOpsService service) { + mContext = context; + mService = service; + } + + /** + * Retrieve current operation state for all applications. + * + * The mode of the ops returned are set for the package but may not reflect their effective + * state due to UID policy or because it's controlled by a different master op. + * + * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)} + * if the effective mode is needed. + * + * @param ops The set of operations you are interested in, or null if you want all of them. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) { + final int opCount = ops.length; + final int[] opCodes = new int[opCount]; + for (int i = 0; i < opCount; i++) { + opCodes[i] = sOpStrToOp.get(ops[i]); + } + final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes); + return (result != null) ? result : Collections.emptyList(); + } + + /** + * Retrieve current operation state for all applications. + * + * The mode of the ops returned are set for the package but may not reflect their effective + * state due to UID policy or because it's controlled by a different master op. + * + * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)} + * if the effective mode is needed. + * + * @param ops The set of operations you are interested in, or null if you want all of them. + * @hide + */ + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + @UnsupportedAppUsage + public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) { + try { + return mService.getPackagesForOps(ops); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve current operation state for one application. + * + * The mode of the ops returned are set for the package but may not reflect their effective + * state due to UID policy or because it's controlled by a different master op. + * + * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)} + * if the effective mode is needed. + * + * @param uid The uid of the application of interest. + * @param packageName The name of the application of interest. + * @param ops The set of operations you are interested in, or null if you want all of them. + * + * @deprecated The int op codes are not stable and you should use the string based op + * names which are stable and namespaced. Use + * {@link #getOpsForPackage(int, String, String...)})}. + * + * @hide + * @removed + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public @NonNull List<PackageOps> getOpsForPackage(int uid, @NonNull String packageName, + @Nullable int[] ops) { + try { + return mService.getOpsForPackage(uid, packageName, ops); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve current operation state for one application. The UID and the + * package must match. + * + * The mode of the ops returned are set for the package but may not reflect their effective + * state due to UID policy or because it's controlled by a different master op. + * + * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)} + * if the effective mode is needed. + * + * @param uid The uid of the application of interest. + * @param packageName The name of the application of interest. + * @param ops The set of operations you are interested in, or null if you want all of them. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid, + @NonNull String packageName, @Nullable String... ops) { + int[] opCodes = null; + if (ops != null) { + opCodes = new int[ops.length]; + for (int i = 0; i < ops.length; i++) { + opCodes[i] = strOpToOp(ops[i]); + } + } + try { + final List<PackageOps> result = mService.getOpsForPackage(uid, packageName, opCodes); + if (result == null) { + return Collections.emptyList(); + } + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve historical app op stats for a period. + * + * @param request A request object describing the data being queried for. + * @param executor Executor on which to run the callback. If <code>null</code> + * the callback is executed on the default executor running on the main thread. + * @param callback Callback on which to deliver the result. + * + * @throws IllegalArgumentException If any of the argument contracts is violated. + * + * @hide + */ + @TestApi + @SystemApi + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public void getHistoricalOps(@NonNull HistoricalOpsRequest request, + @NonNull Executor executor, @NonNull Consumer<HistoricalOps> callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + try { + mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames, + request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags, + new RemoteCallback((result) -> { + final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(ops)); + } finally { + Binder.restoreCallingIdentity(identity); + } + })); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve historical app op stats for a period. + * <p> + * This method queries only the on disk state and the returned ops are raw, + * which is their times are relative to the history start as opposed to the + * epoch start. + * + * @param request A request object describing the data being queried for. + * @param executor Executor on which to run the callback. If <code>null</code> + * the callback is executed on the default executor running on the main thread. + * @param callback Callback on which to deliver the result. + * + * @throws IllegalArgumentException If any of the argument contracts is violated. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void getHistoricalOpsFromDiskRaw(@NonNull HistoricalOpsRequest request, + @Nullable Executor executor, @NonNull Consumer<HistoricalOps> callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + try { + mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName, + request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis, + request.mFlags, new RemoteCallback((result) -> { + final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(ops)); + } finally { + Binder.restoreCallingIdentity(identity); + } + })); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Reloads the non historical state to allow testing the read/write path. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void reloadNonHistoricalState() { + try { + mService.reloadNonHistoricalState(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets given app op in the specified mode for app ops in the UID. + * This applies to all apps currently in the UID or installed in + * this UID in the future. + * + * @param code The app op. + * @param uid The UID for which to set the app. + * @param mode The app op mode to set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + public void setUidMode(int code, int uid, @Mode int mode) { + try { + mService.setUidMode(code, uid, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets given app op in the specified mode for app ops in the UID. + * This applies to all apps currently in the UID or installed in + * this UID in the future. + * + * @param appOp The app op. + * @param uid The UID for which to set the app. + * @param mode The app op mode to set. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + public void setUidMode(String appOp, int uid, @Mode int mode) { + try { + mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void setUserRestriction(int code, boolean restricted, IBinder token) { + setUserRestriction(code, restricted, token, /*exceptionPackages*/null); + } + + /** @hide */ + public void setUserRestriction(int code, boolean restricted, IBinder token, + String[] exceptionPackages) { + setUserRestrictionForUser(code, restricted, token, exceptionPackages, mContext.getUserId()); + } + + /** @hide */ + public void setUserRestrictionForUser(int code, boolean restricted, IBinder token, + String[] exceptionPackages, int userId) { + try { + mService.setUserRestriction(code, restricted, token, userId, exceptionPackages); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + public void setMode(int code, int uid, String packageName, @Mode int mode) { + try { + mService.setMode(code, uid, packageName, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Change the operating mode for the given op in the given app package. You must pass + * in both the uid and name of the application whose mode is being modified; if these + * do not match, the modification will not be applied. + * + * @param op The operation to modify. One of the OPSTR_* constants. + * @param uid The user id of the application whose mode will be changed. + * @param packageName The name of the application package name whose mode will + * be changed. + * @hide + */ + @TestApi + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + public void setMode(String op, int uid, String packageName, @Mode int mode) { + try { + mService.setMode(strOpToOp(op), uid, packageName, mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set a non-persisted restriction on an audio operation at a stream-level. + * Restrictions are temporary additional constraints imposed on top of the persisted rules + * defined by {@link #setMode}. + * + * @param code The operation to restrict. + * @param usage The {@link android.media.AudioAttributes} usage value. + * @param mode The restriction mode (MODE_IGNORED,MODE_ERRORED) or MODE_ALLOWED to unrestrict. + * @param exceptionPackages Optional list of packages to exclude from the restriction. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + @UnsupportedAppUsage + public void setRestriction(int code, @AttributeUsage int usage, @Mode int mode, + String[] exceptionPackages) { + try { + final int uid = Binder.getCallingUid(); + mService.setAudioRestriction(code, usage, uid, mode, exceptionPackages); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + @UnsupportedAppUsage + public void resetAllModes() { + try { + mService.resetAllModes(mContext.getUserId(), null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the app op name associated with a given permission. + * The app op name is one of the public constants defined + * in this class such as {@link #OPSTR_COARSE_LOCATION}. + * This API is intended to be used for mapping runtime + * permissions to the corresponding app op. + * + * @param permission The permission. + * @return The app op associated with the permission or null. + */ + public static String permissionToOp(String permission) { + final Integer opCode = sPermToOp.get(permission); + if (opCode == null) { + return null; + } + return sOpToString[opCode]; + } + + /** + * Monitor for changes to the operating mode for the given op in the given app package. + * You can watch op changes only for your UID. + * + * @param op The operation to monitor, one of OPSTR_*. + * @param packageName The name of the application to monitor. + * @param callback Where to report changes. + */ + public void startWatchingMode(@NonNull String op, @Nullable String packageName, + @NonNull final OnOpChangedListener callback) { + startWatchingMode(strOpToOp(op), packageName, callback); + } + + /** + * Monitor for changes to the operating mode for the given op in the given app package. + * You can watch op changes only for your UID. + * + * @param op The operation to monitor, one of OPSTR_*. + * @param packageName The name of the application to monitor. + * @param flags Option flags: any combination of {@link #WATCH_FOREGROUND_CHANGES} or 0. + * @param callback Where to report changes. + */ + public void startWatchingMode(@NonNull String op, @Nullable String packageName, int flags, + @NonNull final OnOpChangedListener callback) { + startWatchingMode(strOpToOp(op), packageName, flags, callback); + } + + /** + * Monitor for changes to the operating mode for the given op in the given app package. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param op The operation to monitor, one of OP_*. + * @param packageName The name of the application to monitor. + * @param callback Where to report changes. + * @hide + */ + @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingMode(int op, String packageName, final OnOpChangedListener callback) { + startWatchingMode(op, packageName, 0, callback); + } + + /** + * Monitor for changes to the operating mode for the given op in the given app package. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param op The operation to monitor, one of OP_*. + * @param packageName The name of the application to monitor. + * @param flags Option flags: any combination of {@link #WATCH_FOREGROUND_CHANGES} or 0. + * @param callback Where to report changes. + * @hide + */ + @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingMode(int op, String packageName, int flags, + final OnOpChangedListener callback) { + synchronized (mModeWatchers) { + IAppOpsCallback cb = mModeWatchers.get(callback); + if (cb == null) { + cb = new IAppOpsCallback.Stub() { + public void opChanged(int op, int uid, String packageName) { + if (callback instanceof OnOpChangedInternalListener) { + ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName); + } + if (sOpToString[op] != null) { + callback.onOpChanged(sOpToString[op], packageName); + } + } + }; + mModeWatchers.put(callback, cb); + } + try { + mService.startWatchingModeWithFlags(op, packageName, flags, cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Stop monitoring that was previously started with {@link #startWatchingMode}. All + * monitoring associated with this callback will be removed. + */ + public void stopWatchingMode(@NonNull OnOpChangedListener callback) { + synchronized (mModeWatchers) { + IAppOpsCallback cb = mModeWatchers.remove(callback); + if (cb != null) { + try { + mService.stopWatchingMode(cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Start watching for changes to the active state of app ops. An app op may be + * long running and it has a clear start and stop delimiters. If an op is being + * started or stopped by any package you will get a callback. To change the + * watched ops for a registered callback you need to unregister and register it + * again. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param ops The ops to watch. + * @param callback Where to report changes. + * + * @see #isOperationActive(int, int, String) + * @see #stopWatchingActive(OnOpActiveChangedListener) + * @see #startOp(int, int, String) + * @see #finishOp(int, int, String) + * + * @hide + */ + @TestApi + // TODO: Uncomment below annotation once b/73559440 is fixed + // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingActive(@NonNull int[] ops, + @NonNull OnOpActiveChangedListener callback) { + Preconditions.checkNotNull(ops, "ops cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + IAppOpsActiveCallback cb; + synchronized (mActiveWatchers) { + cb = mActiveWatchers.get(callback); + if (cb != null) { + return; + } + cb = new IAppOpsActiveCallback.Stub() { + @Override + public void opActiveChanged(int op, int uid, String packageName, boolean active) { + callback.onOpActiveChanged(op, uid, packageName, active); + } + }; + mActiveWatchers.put(callback, cb); + } + try { + mService.startWatchingActive(ops, cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stop watching for changes to the active state of an app op. An app op may be + * long running and it has a clear start and stop delimiters. Unregistering a + * non-registered callback has no effect. + * + * @see #isOperationActive#(int, int, String) + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #startOp(int, int, String) + * @see #finishOp(int, int, String) + * + * @hide + */ + @TestApi + public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) { + synchronized (mActiveWatchers) { + final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback); + if (cb != null) { + try { + mService.stopWatchingActive(cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Start watching for noted app ops. An app op may be immediate or long running. + * Immediate ops are noted while long running ones are started and stopped. This + * method allows registering a listener to be notified when an app op is noted. If + * an op is being noted by any package you will get a callback. To change the + * watched ops for a registered callback you need to unregister and register it again. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param ops The ops to watch. + * @param callback Where to report changes. + * + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #stopWatchingNoted(OnOpNotedListener) + * @see #noteOp(String, int, String) + * + * @hide + */ + @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener callback) { + IAppOpsNotedCallback cb; + synchronized (mNotedWatchers) { + cb = mNotedWatchers.get(callback); + if (cb != null) { + return; + } + cb = new IAppOpsNotedCallback.Stub() { + @Override + public void opNoted(int op, int uid, String packageName, int mode) { + callback.onOpNoted(op, uid, packageName, mode); + } + }; + mNotedWatchers.put(callback, cb); + } + try { + mService.startWatchingNoted(ops, cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stop watching for noted app ops. An app op may be immediate or long running. + * Unregistering a non-registered callback has no effect. + * + * @see #startWatchingNoted(int[], OnOpNotedListener) + * @see #noteOp(String, int, String) + * + * @hide + */ + public void stopWatchingNoted(@NonNull OnOpNotedListener callback) { + synchronized (mNotedWatchers) { + final IAppOpsNotedCallback cb = mNotedWatchers.get(callback); + if (cb != null) { + try { + mService.stopWatchingNoted(cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + private String buildSecurityExceptionMsg(int op, int uid, String packageName) { + return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op]; + } + + /** + * {@hide} + */ + @TestApi + public static int strOpToOp(@NonNull String op) { + Integer val = sOpStrToOp.get(op); + if (val == null) { + throw new IllegalArgumentException("Unknown operation string: " + op); + } + return val; + } + + /** + * Do a quick check for whether an application might be able to perform an operation. + * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String)} + * or {@link #startOp(String, int, String)} for your actual security checks, which also + * ensure that the given uid and package name are consistent. This function can just be + * used for a quick check to see if an operation has been disabled for the application, + * as an early reject of some work. This does not modify the time stamp or other data + * about the operation. + * + * <p>Important things this will not do (which you need to ultimate use + * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p> + * <ul> + * <li>Verifying the uid and package are consistent, so callers can't spoof + * their identity.</li> + * <li>Taking into account the current foreground/background state of the + * app; apps whose mode varies by this state will always be reported + * as {@link #MODE_ALLOWED}.</li> + * </ul> + * + * @param op The operation to check. One of the OPSTR_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int unsafeCheckOp(@NonNull String op, int uid, @NonNull String packageName) { + return checkOp(strOpToOp(op), uid, packageName); + } + + /** + * @deprecated Renamed to {@link #unsafeCheckOp(String, int, String)}. + */ + @Deprecated + public int checkOp(@NonNull String op, int uid, @NonNull String packageName) { + return checkOp(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + */ + public int unsafeCheckOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + return checkOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * @deprecated Renamed to {@link #unsafeCheckOpNoThrow(String, int, String)}. + */ + @Deprecated + public int checkOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + return checkOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #checkOp} but returns the <em>raw</em> mode associated with the op. + * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. + */ + public int unsafeCheckOpRaw(@NonNull String op, int uid, @NonNull String packageName) { + try { + return mService.checkOperationRaw(strOpToOp(op), uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Like {@link #unsafeCheckOpNoThrow(String, int, String)} but returns the <em>raw</em> + * mode associated with the op. Does not throw a security exception, does not translate + * {@link #MODE_FOREGROUND}. + */ + public int unsafeCheckOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + try { + return mService.checkOperationRaw(strOpToOp(op), uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Make note of an application performing an operation. Note that you must pass + * in both the uid and name of the application to be checked; this function will verify + * that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time. + * @param op The operation to note. One of the OPSTR_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int noteOp(@NonNull String op, int uid, @NonNull String packageName) { + return noteOp(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + */ + public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + return noteOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Make note of an application performing an operation on behalf of another + * application when handling an IPC. Note that you must pass the package name + * of the application that is being proxied while its UID will be inferred from + * the IPC state; this function will verify that the calling uid and proxied + * package name match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for the proxied app and + * your app will be updated to the current time. + * @param op The operation to note. One of the OPSTR_* constants. + * @param proxiedPackageName The name of the application calling into the proxy application. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) { + return noteProxyOp(strOpToOp(op), proxiedPackageName); + } + + /** + * Like {@link #noteProxyOp(String, String)} but instead + * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}. + * + * <p>This API requires the package with the {@code proxiedPackageName} to belongs to + * {@link Binder#getCallingUid()}. + */ + public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) { + return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName); + } + + /** + * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid. + * + * <p>This API requires package with the {@code proxiedPackageName} to belong to + * {@code proxiedUid}. + * + * @param op The op to note + * @param proxiedPackageName The package to note the op for or {@code null} if the op should be + * noted for the "android" package + * @param proxiedUid The uid the package belongs to + */ + public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName, + int proxiedUid) { + return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid); + } + + /** + * Report that an application has started executing a long-running operation. Note that you + * must pass in both the uid and name of the application to be checked; this function will + * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time and the operation will be marked as "running". In this case you must + * later call {@link #finishOp(String, int, String)} to report when the application is no + * longer performing the operation. + * @param op The operation to start. One of the OPSTR_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + */ + public int startOp(@NonNull String op, int uid, @NonNull String packageName) { + return startOp(strOpToOp(op), uid, packageName); + } + + /** + * Like {@link #startOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + */ + public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + return startOpNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Report that an application is no longer performing an operation that had previously + * been started with {@link #startOp(String, int, String)}. There is no validation of input + * or result; the parameters supplied here must be the exact same ones previously passed + * in when starting the operation. + */ + public void finishOp(@NonNull String op, int uid, @NonNull String packageName) { + finishOp(strOpToOp(op), uid, packageName); + } + + /** + * Do a quick check for whether an application might be able to perform an operation. + * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)} + * or {@link #startOp(int, int, String)} for your actual security checks, which also + * ensure that the given uid and package name are consistent. This function can just be + * used for a quick check to see if an operation has been disabled for the application, + * as an early reject of some work. This does not modify the time stamp or other data + * about the operation. + * + * <p>Important things this will not do (which you need to ultimate use + * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p> + * <ul> + * <li>Verifying the uid and package are consistent, so callers can't spoof + * their identity.</li> + * <li>Taking into account the current foreground/background state of the + * app; apps whose mode varies by this state will always be reported + * as {@link #MODE_ALLOWED}.</li> + * </ul> + * + * @param op The operation to check. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + * @hide + */ + @UnsupportedAppUsage + public int checkOp(int op, int uid, String packageName) { + try { + int mode = mService.checkOperation(op, uid, packageName); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); + } + return mode; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ + @UnsupportedAppUsage + public int checkOpNoThrow(int op, int uid, String packageName) { + try { + int mode = mService.checkOperation(op, uid, packageName); + return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Do a quick check to validate if a package name belongs to a UID. + * + * @throws SecurityException if the package name doesn't belong to the given + * UID, or if ownership cannot be verified. + */ + public void checkPackage(int uid, @NonNull String packageName) { + try { + if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) { + throw new SecurityException( + "Package " + packageName + " does not belong to " + uid); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Like {@link #checkOp} but at a stream-level for audio operations. + * @hide + */ + public int checkAudioOp(int op, int stream, int uid, String packageName) { + try { + final int mode = mService.checkAudioOperation(op, stream, uid, packageName); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); + } + return mode; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Like {@link #checkAudioOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ + public int checkAudioOpNoThrow(int op, int stream, int uid, String packageName) { + try { + return mService.checkAudioOperation(op, stream, uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Make note of an application performing an operation. Note that you must pass + * in both the uid and name of the application to be checked; this function will verify + * that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time. + * @param op The operation to note. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + * @hide + */ + @UnsupportedAppUsage + public int noteOp(int op, int uid, String packageName) { + final int mode = noteOpNoThrow(op, uid, packageName); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); + } + return mode; + } + + /** + * Make note of an application performing an operation on behalf of another + * application when handling an IPC. Note that you must pass the package name + * of the application that is being proxied while its UID will be inferred from + * the IPC state; this function will verify that the calling uid and proxied + * package name match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for the proxied app and + * your app will be updated to the current time. + * @param op The operation to note. One of the OPSTR_* constants. + * @param proxiedPackageName The name of the application calling into the proxy application. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the proxy or proxied app has been configured to + * crash on this op. + * + * @hide + */ + @UnsupportedAppUsage + public int noteProxyOp(int op, String proxiedPackageName) { + int mode = noteProxyOpNoThrow(op, proxiedPackageName); + if (mode == MODE_ERRORED) { + throw new SecurityException("Proxy package " + mContext.getOpPackageName() + + " from uid " + Process.myUid() + " or calling package " + + proxiedPackageName + " from uid " + Binder.getCallingUid() + + " not allowed to perform " + sOpNames[op]); + } + return mode; + } + + /** + * Like {@link #noteProxyOp(int, String)} but instead + * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}. + * @hide + */ + public int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) { + try { + return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(), + proxiedUid, proxiedPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Like {@link #noteProxyOp(int, String)} but instead + * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}. + * + * <p>This API requires the package with {@code proxiedPackageName} to belongs to + * {@link Binder#getCallingUid()}. + * + * @hide + */ + public int noteProxyOpNoThrow(int op, String proxiedPackageName) { + return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid()); + } + + /** + * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ + @UnsupportedAppUsage + public int noteOpNoThrow(int op, int uid, String packageName) { + try { + return mService.noteOperation(op, uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @UnsupportedAppUsage + public int noteOp(int op) { + return noteOp(op, Process.myUid(), mContext.getOpPackageName()); + } + + /** @hide */ + @UnsupportedAppUsage + public static IBinder getToken(IAppOpsService service) { + synchronized (AppOpsManager.class) { + if (sToken != null) { + return sToken; + } + try { + sToken = service.getToken(new Binder()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return sToken; + } + } + + /** @hide */ + public int startOp(int op) { + return startOp(op, Process.myUid(), mContext.getOpPackageName()); + } + + /** + * Report that an application has started executing a long-running operation. Note that you + * must pass in both the uid and name of the application to be checked; this function will + * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call + * succeeds, the last execution time of the operation for this app will be updated to + * the current time and the operation will be marked as "running". In this case you must + * later call {@link #finishOp(int, int, String)} to report when the application is no + * longer performing the operation. + * + * @param op The operation to start. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @throws SecurityException If the app has been configured to crash on this op. + * @hide + */ + public int startOp(int op, int uid, String packageName) { + return startOp(op, uid, packageName, false); + } + + /** + * Report that an application has started executing a long-running operation. Similar + * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT} + * the operation should succeed since the caller has performed its standard permission + * checks which passed and would perform the protected operation for this mode. + * + * @param op The operation to start. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}. + * + * @throws SecurityException If the app has been configured to crash on this op or + * the package is not in the passed in UID. + * + * @hide + */ + public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) { + final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault); + if (mode == MODE_ERRORED) { + throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); + } + return mode; + } + + /** + * Like {@link #startOp} but instead of throwing a {@link SecurityException} it + * returns {@link #MODE_ERRORED}. + * @hide + */ + public int startOpNoThrow(int op, int uid, String packageName) { + return startOpNoThrow(op, uid, packageName, false); + } + + /** + * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a + * {@link SecurityException} it returns {@link #MODE_ERRORED}. + * + * @param op The operation to start. One of the OP_* constants. + * @param uid The user id of the application attempting to perform the operation. + * @param packageName The name of the application attempting to perform the operation. + * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or + * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without + * causing the app to crash). + * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}. + * + * @hide + */ + public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) { + try { + return mService.startOperation(getToken(mService), op, uid, packageName, + startIfModeDefault); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report that an application is no longer performing an operation that had previously + * been started with {@link #startOp(int, int, String)}. There is no validation of input + * or result; the parameters supplied here must be the exact same ones previously passed + * in when starting the operation. + * @hide + */ + public void finishOp(int op, int uid, String packageName) { + try { + mService.finishOperation(getToken(mService), op, uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void finishOp(int op) { + finishOp(op, Process.myUid(), mContext.getOpPackageName()); + } + + /** + * Checks whether the given op for a UID and package is active. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can query only for your UID. + * + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #stopWatchingMode(OnOpChangedListener) + * @see #finishOp(int) + * @see #startOp(int) + * + * @hide */ + @TestApi + // TODO: Uncomment below annotation once b/73559440 is fixed + // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public boolean isOperationActive(int code, int uid, String packageName) { + try { + return mService.isOperationActive(code, uid, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures the app ops persistence for testing. + * + * @param mode The mode in which the historical registry operates. + * @param baseSnapshotInterval The base interval on which we would be persisting a snapshot of + * the historical data. The history is recursive where every subsequent step encompasses + * {@code compressionStep} longer interval with {@code compressionStep} distance between + * snapshots. + * @param compressionStep The compression step in every iteration. + * + * @see #HISTORICAL_MODE_DISABLED + * @see #HISTORICAL_MODE_ENABLED_ACTIVE + * @see #HISTORICAL_MODE_ENABLED_PASSIVE + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void setHistoryParameters(@HistoricalMode int mode, long baseSnapshotInterval, + int compressionStep) { + try { + mService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Offsets the history by the given duration. + * + * @param offsetMillis The offset duration. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void offsetHistory(long offsetMillis) { + try { + mService.offsetHistory(offsetMillis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds ops to the history directly. This could be useful for testing especially + * when the historical registry operates in {@link #HISTORICAL_MODE_ENABLED_PASSIVE} + * mode. + * + * @param ops The ops to add to the history. + * + * @see #setHistoryParameters(int, long, int) + * @see #HISTORICAL_MODE_ENABLED_PASSIVE + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void addHistoricalOps(@NonNull HistoricalOps ops) { + try { + mService.addHistoricalOps(ops); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resets the app ops persistence for testing. + * + * @see #setHistoryParameters(int, long, int) + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void resetHistoryParameters() { + try { + mService.resetHistoryParameters(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Clears all app ops history. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void clearHistory() { + try { + mService.clearHistory(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns all supported operation names. + * @hide + */ + @SystemApi + @TestApi + public static String[] getOpStrs() { + return Arrays.copyOf(sOpToString, sOpToString.length); + } + + + /** + * @return number of App ops + * @hide + */ + @TestApi + public static int getNumOps() { + return _NUM_OP; + } + + /** + * Computes the max for the given flags in between the begin and + * end UID states. + * + * @param counts The data array. + * @param flags The UID flags. + * @param beginUidState The beginning UID state (exclusive). + * @param endUidState The end UID state. + * @return The sum. + */ + private static long maxForFlagsInStates(@Nullable LongSparseLongArray counts, + @UidState int beginUidState, @UidState int endUidState, + @OpFlags int flags) { + if (counts == null) { + return 0; + } + long max = 0; + while (flags != 0) { + final int flag = 1 << Integer.numberOfTrailingZeros(flags); + flags &= ~flag; + for (int uidState : UID_STATES) { + if (uidState < beginUidState || uidState > endUidState) { + continue; + } + final long key = makeKey(uidState, flag); + max = Math.max(max, counts.get(key)); + } + } + return max; + } + + + private static void writeLongSparseLongArrayToParcel( + @Nullable LongSparseLongArray array, @NonNull Parcel parcel) { + if (array != null) { + final int size = array.size(); + parcel.writeInt(size); + for (int i = 0; i < size; i++) { + parcel.writeLong(array.keyAt(i)); + parcel.writeLong(array.valueAt(i)); + } + } else { + parcel.writeInt(-1); + } + } + + private static @Nullable LongSparseLongArray readLongSparseLongArrayFromParcel( + @NonNull Parcel parcel) { + final int size = parcel.readInt(); + if (size < 0) { + return null; + } + final LongSparseLongArray array = new LongSparseLongArray(size); + for (int i = 0; i < size; i++) { + array.append(parcel.readLong(), parcel.readLong()); + } + return array; + } + + private static void writeLongSparseStringArrayToParcel( + @Nullable LongSparseArray<String> array, @NonNull Parcel parcel) { + if (array != null) { + final int size = array.size(); + parcel.writeInt(size); + for (int i = 0; i < size; i++) { + parcel.writeLong(array.keyAt(i)); + parcel.writeString(array.valueAt(i)); + } + } else { + parcel.writeInt(-1); + } + } + + private static @Nullable LongSparseArray<String> readLongSparseStringArrayFromParcel( + @NonNull Parcel parcel) { + final int size = parcel.readInt(); + if (size < 0) { + return null; + } + final LongSparseArray<String> array = new LongSparseArray<>(size); + for (int i = 0; i < size; i++) { + array.append(parcel.readLong(), parcel.readString()); + } + return array; + } + + /** + * Collects the keys from an array to the result creating the result if needed. + * + * @param array The array whose keys to collect. + * @param result The optional result store collected keys. + * @return The result collected keys array. + */ + private static LongSparseArray<Object> collectKeys(@Nullable LongSparseLongArray array, + @Nullable LongSparseArray<Object> result) { + if (array != null) { + if (result == null) { + result = new LongSparseArray<>(); + } + final int accessSize = array.size(); + for (int i = 0; i < accessSize; i++) { + result.put(array.keyAt(i), null); + } + } + return result; + } + + /** @hide */ + public static String uidStateToString(@UidState int uidState) { + switch (uidState) { + case UID_STATE_PERSISTENT: { + return "UID_STATE_PERSISTENT"; + } + case UID_STATE_TOP: { + return "UID_STATE_TOP"; + } + case UID_STATE_FOREGROUND_SERVICE_LOCATION: { + return "UID_STATE_FOREGROUND_SERVICE_LOCATION"; + } + case UID_STATE_FOREGROUND_SERVICE: { + return "UID_STATE_FOREGROUND_SERVICE"; + } + case UID_STATE_FOREGROUND: { + return "UID_STATE_FOREGROUND"; + } + case UID_STATE_BACKGROUND: { + return "UID_STATE_BACKGROUND"; + } + case UID_STATE_CACHED: { + return "UID_STATE_CACHED"; + } + default: { + return "UNKNOWN"; + } + } + } + + /** @hide */ + public static int parseHistoricalMode(@NonNull String mode) { + switch (mode) { + case "HISTORICAL_MODE_ENABLED_ACTIVE": { + return HISTORICAL_MODE_ENABLED_ACTIVE; + } + case "HISTORICAL_MODE_ENABLED_PASSIVE": { + return HISTORICAL_MODE_ENABLED_PASSIVE; + } + default: { + return HISTORICAL_MODE_DISABLED; + } + } + } + + /** @hide */ + public static String historicalModeToString(@HistoricalMode int mode) { + switch (mode) { + case HISTORICAL_MODE_DISABLED: { + return "HISTORICAL_MODE_DISABLED"; + } + case HISTORICAL_MODE_ENABLED_ACTIVE: { + return "HISTORICAL_MODE_ENABLED_ACTIVE"; + } + case HISTORICAL_MODE_ENABLED_PASSIVE: { + return "HISTORICAL_MODE_ENABLED_PASSIVE"; + } + default: { + return "UNKNOWN"; + } + } + } + + private static int getSystemAlertWindowDefault() { + final Context context = ActivityThread.currentApplication(); + if (context == null) { + return AppOpsManager.MODE_DEFAULT; + } + + // system alert window is disable on low ram phones starting from Q + final PackageManager pm = context.getPackageManager(); + // TVs are constantly plugged in and has less concern for memory/power + if (ActivityManager.isLowRamDeviceStatic() + && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) { + return AppOpsManager.MODE_IGNORED; + } + + return AppOpsManager.MODE_DEFAULT; + } +}
diff --git a/android/app/AppOpsManagerInternal.java b/android/app/AppOpsManagerInternal.java new file mode 100644 index 0000000..08cad04 --- /dev/null +++ b/android/app/AppOpsManagerInternal.java
@@ -0,0 +1,114 @@ +/* + * 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 android.annotation.NonNull; +import android.util.SparseIntArray; + +import com.android.internal.util.function.QuadFunction; +import com.android.internal.util.function.TriFunction; + +/** + * App ops service local interface. + * + * @hide Only for use within the system server. + */ +public abstract class AppOpsManagerInternal { + /** Interface to override app ops checks via composition */ + public interface CheckOpsDelegate { + /** + * Allows overriding check operation behavior. + * + * @param code The op code to check. + * @param uid The UID for which to check. + * @param packageName The package for which to check. + * @param superImpl The super implementation. + * @param raw Whether to check the raw op i.e. not interpret the mode based on UID state. + * @return The app op check result. + */ + int checkOperation(int code, int uid, String packageName, boolean raw, + QuadFunction<Integer, Integer, String, Boolean, Integer> superImpl); + + /** + * Allows overriding check audio operation behavior. + * + * @param code The op code to check. + * @param usage The audio op usage. + * @param uid The UID for which to check. + * @param packageName The package for which to check. + * @param superImpl The super implementation. + * @return The app op check result. + */ + int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction<Integer, Integer, Integer, String, Integer> superImpl); + + /** + * Allows overriding note operation behavior. + * + * @param code The op code to note. + * @param uid The UID for which to note. + * @param packageName The package for which to note. + * @param superImpl The super implementation. + * @return The app op note result. + */ + int noteOperation(int code, int uid, String packageName, + TriFunction<Integer, Integer, String, Integer> superImpl); + } + + /** + * Set the currently configured device and profile owners. Specifies the package uid (value) + * that has been configured for each user (key) that has one. These will be allowed privileged + * access to app ops for their user. + */ + public abstract void setDeviceAndProfileOwners(SparseIntArray owners); + + /** + * Sets the app-ops mode for a certain app-op and uid. + * + * <p>Similar as {@link AppOpsManager#setUidMode} but does not require the package manager to be + * working. Hence this can be used very early during boot. + * + * <p>Only for internal callers. Does <u>not</u> verify that package name belongs to uid. + * + * @param code The op code to set. + * @param uid The UID for which to set. + * @param mode The new mode to set. + */ + public abstract void setUidMode(int code, int uid, int mode); + + /** + * Set all {@link #setMode (package) modes} for this uid to the default value. + * + * @param code The app-op + * @param uid The uid + */ + public abstract void setAllPkgModesToDefault(int code, int uid); + + /** + * Get the (raw) mode of an app-op. + * + * <p>Does <u>not</u> verify that package belongs to uid. The caller needs to do that. + * + * @param code The code of the op + * @param uid The uid of the package the op belongs to + * @param packageName The package the op belongs to + * + * @return The mode of the op + */ + public abstract @AppOpsManager.Mode int checkOperationUnchecked(int code, int uid, + @NonNull String packageName); +}
diff --git a/android/app/Application.java b/android/app/Application.java new file mode 100644 index 0000000..e12942f --- /dev/null +++ b/android/app/Application.java
@@ -0,0 +1,655 @@ +/* + * 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.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; +import android.content.ComponentCallbacks; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.util.Log; +import android.view.autofill.AutofillManager; + +import java.util.ArrayList; + +/** + * Base class for maintaining global application state. You can provide your own + * implementation by creating a subclass and specifying the fully-qualified name + * of this subclass as the <code>"android:name"</code> attribute in your + * AndroidManifest.xml's <code><application></code> tag. The Application + * class, or your subclass of the Application class, is instantiated before any + * other class when the process for your application/package is created. + * + * <p class="note"><strong>Note: </strong>There is normally no need to subclass + * Application. In most situations, static singletons can provide the same + * functionality in a more modular way. If your singleton needs a global + * context (for example to register broadcast receivers), include + * {@link android.content.Context#getApplicationContext() Context.getApplicationContext()} + * as a {@link android.content.Context} argument when invoking your singleton's + * <code>getInstance()</code> method. + * </p> + */ +public class Application extends ContextWrapper implements ComponentCallbacks2 { + private static final String TAG = "Application"; + @UnsupportedAppUsage + private ArrayList<ComponentCallbacks> mComponentCallbacks = + new ArrayList<ComponentCallbacks>(); + @UnsupportedAppUsage + private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks = + new ArrayList<ActivityLifecycleCallbacks>(); + @UnsupportedAppUsage + private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null; + + /** @hide */ + @UnsupportedAppUsage + public LoadedApk mLoadedApk; + + public interface ActivityLifecycleCallbacks { + + /** + * Called as the first step of the Activity being created. This is always called before + * {@link Activity#onCreate}. + */ + default void onActivityPreCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { + } + + /** + * Called when the Activity calls {@link Activity#onCreate super.onCreate()}. + */ + void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState); + + /** + * Called as the last step of the Activity being created. This is always called after + * {@link Activity#onCreate}. + */ + default void onActivityPostCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { + } + + /** + * Called as the first step of the Activity being started. This is always called before + * {@link Activity#onStart}. + */ + default void onActivityPreStarted(@NonNull Activity activity) { + } + + /** + * Called when the Activity calls {@link Activity#onStart super.onStart()}. + */ + void onActivityStarted(@NonNull Activity activity); + + /** + * Called as the last step of the Activity being started. This is always called after + * {@link Activity#onStart}. + */ + default void onActivityPostStarted(@NonNull Activity activity) { + } + + /** + * Called as the first step of the Activity being resumed. This is always called before + * {@link Activity#onResume}. + */ + default void onActivityPreResumed(@NonNull Activity activity) { + } + + /** + * Called when the Activity calls {@link Activity#onResume super.onResume()}. + */ + void onActivityResumed(@NonNull Activity activity); + + /** + * Called as the last step of the Activity being resumed. This is always called after + * {@link Activity#onResume} and {@link Activity#onPostResume}. + */ + default void onActivityPostResumed(@NonNull Activity activity) { + } + + /** + * Called as the first step of the Activity being paused. This is always called before + * {@link Activity#onPause}. + */ + default void onActivityPrePaused(@NonNull Activity activity) { + } + + /** + * Called when the Activity calls {@link Activity#onPause super.onPause()}. + */ + void onActivityPaused(@NonNull Activity activity); + + /** + * Called as the last step of the Activity being paused. This is always called after + * {@link Activity#onPause}. + */ + default void onActivityPostPaused(@NonNull Activity activity) { + } + + /** + * Called as the first step of the Activity being stopped. This is always called before + * {@link Activity#onStop}. + */ + default void onActivityPreStopped(@NonNull Activity activity) { + } + + /** + * Called when the Activity calls {@link Activity#onStop super.onStop()}. + */ + void onActivityStopped(@NonNull Activity activity); + + /** + * Called as the last step of the Activity being stopped. This is always called after + * {@link Activity#onStop}. + */ + default void onActivityPostStopped(@NonNull Activity activity) { + } + + /** + * Called as the first step of the Activity saving its instance state. This is always + * called before {@link Activity#onSaveInstanceState}. + */ + default void onActivityPreSaveInstanceState(@NonNull Activity activity, + @NonNull Bundle outState) { + } + + /** + * Called when the Activity calls + * {@link Activity#onSaveInstanceState super.onSaveInstanceState()}. + */ + void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState); + + /** + * Called as the last step of the Activity saving its instance state. This is always + * called after{@link Activity#onSaveInstanceState}. + */ + default void onActivityPostSaveInstanceState(@NonNull Activity activity, + @NonNull Bundle outState) { + } + + /** + * Called as the first step of the Activity being destroyed. This is always called before + * {@link Activity#onDestroy}. + */ + default void onActivityPreDestroyed(@NonNull Activity activity) { + } + + /** + * Called when the Activity calls {@link Activity#onDestroy super.onDestroy()}. + */ + void onActivityDestroyed(@NonNull Activity activity); + + /** + * Called as the last step of the Activity being destroyed. This is always called after + * {@link Activity#onDestroy}. + */ + default void onActivityPostDestroyed(@NonNull Activity activity) { + } + } + + /** + * Callback interface for use with {@link Application#registerOnProvideAssistDataListener} + * and {@link Application#unregisterOnProvideAssistDataListener}. + */ + public interface OnProvideAssistDataListener { + /** + * 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. + */ + public void onProvideAssistData(Activity activity, Bundle data); + } + + public Application() { + super(null); + } + + /** + * Called when the application is starting, before any activity, service, + * or receiver objects (excluding content providers) have been created. + * + * <p>Implementations should be as quick as possible (for example using + * lazy initialization of state) since the time spent in this function + * directly impacts the performance of starting the first activity, + * service, or receiver in a process.</p> + * + * <p>If you override this method, be sure to call {@code super.onCreate()}.</p> + * + * <p class="note">Be aware that direct boot may also affect callback order on + * Android {@link android.os.Build.VERSION_CODES#N} and later devices. + * Until the user unlocks the device, only direct boot aware components are + * allowed to run. You should consider that all direct boot unaware + * components, including such {@link android.content.ContentProvider}, are + * disabled until user unlock happens, especially when component callback + * order matters.</p> + */ + @CallSuper + public void onCreate() { + } + + /** + * This method is for use in emulated process environments. It will + * never be called on a production Android device, where processes are + * removed by simply killing them; no user code (including this callback) + * is executed when doing so. + */ + @CallSuper + public void onTerminate() { + } + + @CallSuper + public void onConfigurationChanged(@NonNull Configuration newConfig) { + Object[] callbacks = collectComponentCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig); + } + } + } + + @CallSuper + public void onLowMemory() { + Object[] callbacks = collectComponentCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ComponentCallbacks)callbacks[i]).onLowMemory(); + } + } + } + + @CallSuper + public void onTrimMemory(int level) { + Object[] callbacks = collectComponentCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + Object c = callbacks[i]; + if (c instanceof ComponentCallbacks2) { + ((ComponentCallbacks2)c).onTrimMemory(level); + } + } + } + } + + public void registerComponentCallbacks(ComponentCallbacks callback) { + synchronized (mComponentCallbacks) { + mComponentCallbacks.add(callback); + } + } + + public void unregisterComponentCallbacks(ComponentCallbacks callback) { + synchronized (mComponentCallbacks) { + mComponentCallbacks.remove(callback); + } + } + + public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) { + synchronized (mActivityLifecycleCallbacks) { + mActivityLifecycleCallbacks.add(callback); + } + } + + public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) { + synchronized (mActivityLifecycleCallbacks) { + mActivityLifecycleCallbacks.remove(callback); + } + } + + public void registerOnProvideAssistDataListener(OnProvideAssistDataListener callback) { + synchronized (this) { + if (mAssistCallbacks == null) { + mAssistCallbacks = new ArrayList<OnProvideAssistDataListener>(); + } + mAssistCallbacks.add(callback); + } + } + + public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) { + synchronized (this) { + if (mAssistCallbacks != null) { + mAssistCallbacks.remove(callback); + } + } + } + + /** + * Returns the name of the current process. A package's default process name + * is the same as its package name. Non-default processes will look like + * "$PACKAGE_NAME:$NAME", where $NAME corresponds to an android:process + * attribute within AndroidManifest.xml. + */ + public static String getProcessName() { + return ActivityThread.currentProcessName(); + } + + // ------------------ Internal API ------------------ + + /** + * @hide + */ + @UnsupportedAppUsage + /* package */ final void attach(Context context) { + attachBaseContext(context); + mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPreCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreCreated(activity, + savedInstanceState); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivityCreated(activity, + savedInstanceState); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostCreated(activity, + savedInstanceState); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPreStarted(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStarted(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityStarted(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivityStarted(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostStarted(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostStarted(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPreResumed(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreResumed(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityResumed(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivityResumed(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostResumed(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostResumed(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPrePaused(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPrePaused(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPaused(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivityPaused(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostPaused(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostPaused(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPreStopped(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStopped(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityStopped(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivityStopped(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostStopped(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostStopped(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPreSaveInstanceState(@NonNull Activity activity, + @NonNull Bundle outState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreSaveInstanceState( + activity, outState); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivitySaveInstanceState(@NonNull Activity activity, + @NonNull Bundle outState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivitySaveInstanceState(activity, + outState); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostSaveInstanceState(@NonNull Activity activity, + @NonNull Bundle outState) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostSaveInstanceState( + activity, outState); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPreDestroyed(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreDestroyed(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityDestroyed(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((ActivityLifecycleCallbacks)callbacks[i]).onActivityDestroyed(activity); + } + } + } + + @UnsupportedAppUsage + /* package */ void dispatchActivityPostDestroyed(@NonNull Activity activity) { + Object[] callbacks = collectActivityLifecycleCallbacks(); + if (callbacks != null) { + for (int i = 0; i < callbacks.length; i++) { + ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostDestroyed(activity); + } + } + } + + private Object[] collectComponentCallbacks() { + Object[] callbacks = null; + synchronized (mComponentCallbacks) { + if (mComponentCallbacks.size() > 0) { + callbacks = mComponentCallbacks.toArray(); + } + } + return callbacks; + } + + @UnsupportedAppUsage + private Object[] collectActivityLifecycleCallbacks() { + Object[] callbacks = null; + synchronized (mActivityLifecycleCallbacks) { + if (mActivityLifecycleCallbacks.size() > 0) { + callbacks = mActivityLifecycleCallbacks.toArray(); + } + } + return callbacks; + } + + /* package */ void dispatchOnProvideAssistData(Activity activity, Bundle data) { + Object[] callbacks; + synchronized (this) { + if (mAssistCallbacks == null) { + return; + } + callbacks = mAssistCallbacks.toArray(); + } + if (callbacks != null) { + for (int i=0; i<callbacks.length; i++) { + ((OnProvideAssistDataListener)callbacks[i]).onProvideAssistData(activity, data); + } + } + } + + /** @hide */ + @Override + public AutofillManager.AutofillClient getAutofillClient() { + final AutofillManager.AutofillClient client = super.getAutofillClient(); + if (client != null) { + return client; + } + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "getAutofillClient(): null on super, trying to find activity thread"); + } + // Okay, ppl use the application context when they should not. This breaks + // autofill among other things. We pick the focused activity since autofill + // interacts only with the currently focused activity and we need the fill + // client only if a call comes from the focused activity. Sigh... + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + return null; + } + final int activityCount = activityThread.mActivities.size(); + for (int i = 0; i < activityCount; i++) { + final ActivityThread.ActivityClientRecord record = + activityThread.mActivities.valueAt(i); + if (record == null) { + continue; + } + final Activity activity = record.activity; + if (activity == null) { + continue; + } + if (activity.getWindow().getDecorView().hasFocus()) { + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "getAutofillClient(): found activity for " + this + ": " + activity); + } + return activity; + } + } + if (android.view.autofill.Helper.sVerbose) { + Log.v(TAG, "getAutofillClient(): none of the " + activityCount + " activities on " + + this + " have focus"); + } + return null; + } +}
diff --git a/android/app/ApplicationErrorReport.java b/android/app/ApplicationErrorReport.java new file mode 100644 index 0000000..a0b3dc0 --- /dev/null +++ b/android/app/ApplicationErrorReport.java
@@ -0,0 +1,710 @@ +/* + * 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.app; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Binder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemProperties; +import android.provider.Settings; +import android.util.Printer; +import android.util.Slog; + +import com.android.internal.util.FastPrintWriter; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Describes an application error. + * + * A report has a type, which is one of + * <ul> + * <li> {@link #TYPE_NONE} uninitialized instance of {@link ApplicationErrorReport}. + * <li> {@link #TYPE_CRASH} application crash. Information about the crash + * is stored in {@link #crashInfo}. + * <li> {@link #TYPE_ANR} application not responding. Information about the + * ANR is stored in {@link #anrInfo}. + * <li> {@link #TYPE_BATTERY} user reported application is using too much + * battery. Information about the battery use is stored in {@link #batteryInfo}. + * <li> {@link #TYPE_RUNNING_SERVICE} user reported application is leaving an + * unneeded serive running. Information about the battery use is stored in + * {@link #runningServiceInfo}. + * </ul> + */ + +public class ApplicationErrorReport implements Parcelable { + // System property defining error report receiver for system apps + static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps"; + + // System property defining default error report receiver + static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default"; + + /** + * Uninitialized error report. + */ + public static final int TYPE_NONE = 0; + + /** + * An error report about an application crash. + */ + public static final int TYPE_CRASH = 1; + + /** + * An error report about an application that's not responding. + */ + public static final int TYPE_ANR = 2; + + /** + * An error report about an application that's consuming too much battery. + */ + public static final int TYPE_BATTERY = 3; + + /** + * A report from a user to a developer about a running service that the + * user doesn't think should be running. + */ + public static final int TYPE_RUNNING_SERVICE = 5; + + /** + * Type of this report. Can be one of {@link #TYPE_NONE}, + * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, {@link #TYPE_BATTERY}, + * or {@link #TYPE_RUNNING_SERVICE}. + */ + public int type; + + /** + * Package name of the application. + */ + public String packageName; + + /** + * Package name of the application which installed the application this + * report pertains to. + * This identifies which market the application came from. + */ + public String installerPackageName; + + /** + * Process name of the application. + */ + public String processName; + + /** + * Time at which the error occurred. + */ + public long time; + + /** + * Set if the app is on the system image. + */ + public boolean systemApp; + + /** + * If this report is of type {@link #TYPE_CRASH}, contains an instance + * of CrashInfo describing the crash; otherwise null. + */ + public CrashInfo crashInfo; + + /** + * If this report is of type {@link #TYPE_ANR}, contains an instance + * of AnrInfo describing the ANR; otherwise null. + */ + public AnrInfo anrInfo; + + /** + * If this report is of type {@link #TYPE_BATTERY}, contains an instance + * of BatteryInfo; otherwise null. + */ + public BatteryInfo batteryInfo; + + /** + * If this report is of type {@link #TYPE_RUNNING_SERVICE}, contains an instance + * of RunningServiceInfo; otherwise null. + */ + public RunningServiceInfo runningServiceInfo; + + /** + * Create an uninitialized instance of {@link ApplicationErrorReport}. + */ + public ApplicationErrorReport() { + } + + /** + * Create an instance of {@link ApplicationErrorReport} initialized from + * a parcel. + */ + ApplicationErrorReport(Parcel in) { + readFromParcel(in); + } + + public static ComponentName getErrorReportReceiver(Context context, + String packageName, int appFlags) { + // check if error reporting is enabled in secure settings + int enabled = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SEND_ACTION_APP_ERROR, 0); + if (enabled == 0) { + return null; + } + + PackageManager pm = context.getPackageManager(); + + // look for receiver in the installer package + String candidate = null; + ComponentName result = null; + + try { + candidate = pm.getInstallerPackageName(packageName); + } catch (IllegalArgumentException e) { + // the package could already removed + } + + if (candidate != null) { + result = getErrorReportReceiver(pm, packageName, candidate); + if (result != null) { + return result; + } + } + + // if the error app is on the system image, look for system apps + // error receiver + if ((appFlags&ApplicationInfo.FLAG_SYSTEM) != 0) { + candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY); + result = getErrorReportReceiver(pm, packageName, candidate); + if (result != null) { + return result; + } + } + + // if there is a default receiver, try that + candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY); + return getErrorReportReceiver(pm, packageName, candidate); + } + + /** + * Return activity in receiverPackage that handles ACTION_APP_ERROR. + * + * @param pm PackageManager instance + * @param errorPackage package which caused the error + * @param receiverPackage candidate package to receive the error + * @return activity component within receiverPackage which handles + * ACTION_APP_ERROR, or null if not found + */ + static ComponentName getErrorReportReceiver(PackageManager pm, String errorPackage, + String receiverPackage) { + if (receiverPackage == null || receiverPackage.length() == 0) { + return null; + } + + // break the loop if it's the error report receiver package that crashed + if (receiverPackage.equals(errorPackage)) { + return null; + } + + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + intent.setPackage(receiverPackage); + ResolveInfo info = pm.resolveActivity(intent, 0); + if (info == null || info.activityInfo == null) { + return null; + } + return new ComponentName(receiverPackage, info.activityInfo.name); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(type); + dest.writeString(packageName); + dest.writeString(installerPackageName); + dest.writeString(processName); + dest.writeLong(time); + dest.writeInt(systemApp ? 1 : 0); + dest.writeInt(crashInfo != null ? 1 : 0); + + switch (type) { + case TYPE_CRASH: + if (crashInfo != null) { + crashInfo.writeToParcel(dest, flags); + } + break; + case TYPE_ANR: + anrInfo.writeToParcel(dest, flags); + break; + case TYPE_BATTERY: + batteryInfo.writeToParcel(dest, flags); + break; + case TYPE_RUNNING_SERVICE: + runningServiceInfo.writeToParcel(dest, flags); + break; + } + } + + public void readFromParcel(Parcel in) { + type = in.readInt(); + packageName = in.readString(); + installerPackageName = in.readString(); + processName = in.readString(); + time = in.readLong(); + systemApp = in.readInt() == 1; + boolean hasCrashInfo = in.readInt() == 1; + + switch (type) { + case TYPE_CRASH: + crashInfo = hasCrashInfo ? new CrashInfo(in) : null; + anrInfo = null; + batteryInfo = null; + runningServiceInfo = null; + break; + case TYPE_ANR: + anrInfo = new AnrInfo(in); + crashInfo = null; + batteryInfo = null; + runningServiceInfo = null; + break; + case TYPE_BATTERY: + batteryInfo = new BatteryInfo(in); + anrInfo = null; + crashInfo = null; + runningServiceInfo = null; + break; + case TYPE_RUNNING_SERVICE: + batteryInfo = null; + anrInfo = null; + crashInfo = null; + runningServiceInfo = new RunningServiceInfo(in); + break; + } + } + + /** + * Describes an application crash. + */ + public static class CrashInfo { + /** + * Class name of the exception that caused the crash. + */ + public String exceptionClassName; + + /** + * Message stored in the exception. + */ + public String exceptionMessage; + + /** + * File which the exception was thrown from. + */ + public String throwFileName; + + /** + * Class which the exception was thrown from. + */ + public String throwClassName; + + /** + * Method which the exception was thrown from. + */ + public String throwMethodName; + + /** + * Line number the exception was thrown from. + */ + public int throwLineNumber; + + /** + * Stack trace. + */ + public String stackTrace; + + /** + * Crash tag for some context. + * @hide + */ + public String crashTag; + + /** + * Create an uninitialized instance of CrashInfo. + */ + public CrashInfo() { + } + + /** + * Create an instance of CrashInfo initialized from an exception. + */ + public CrashInfo(Throwable tr) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw, false, 256); + tr.printStackTrace(pw); + pw.flush(); + stackTrace = sanitizeString(sw.toString()); + exceptionMessage = tr.getMessage(); + + // Populate fields with the "root cause" exception + Throwable rootTr = tr; + while (tr.getCause() != null) { + tr = tr.getCause(); + if (tr.getStackTrace() != null && tr.getStackTrace().length > 0) { + rootTr = tr; + } + String msg = tr.getMessage(); + if (msg != null && msg.length() > 0) { + exceptionMessage = msg; + } + } + + exceptionClassName = rootTr.getClass().getName(); + if (rootTr.getStackTrace().length > 0) { + StackTraceElement trace = rootTr.getStackTrace()[0]; + throwFileName = trace.getFileName(); + throwClassName = trace.getClassName(); + throwMethodName = trace.getMethodName(); + throwLineNumber = trace.getLineNumber(); + } else { + throwFileName = "unknown"; + throwClassName = "unknown"; + throwMethodName = "unknown"; + throwLineNumber = 0; + } + + exceptionMessage = sanitizeString(exceptionMessage); + } + + /** {@hide} */ + public void appendStackTrace(String tr) { + stackTrace = sanitizeString(stackTrace + tr); + } + + /** + * Ensure that the string is of reasonable size, truncating from the middle if needed. + */ + private String sanitizeString(String s) { + int prefixLength = 10 * 1024; + int suffixLength = 10 * 1024; + int acceptableLength = prefixLength + suffixLength; + + if (s != null && s.length() > acceptableLength) { + String replacement = + "\n[TRUNCATED " + (s.length() - acceptableLength) + " CHARS]\n"; + + StringBuilder sb = new StringBuilder(acceptableLength + replacement.length()); + sb.append(s.substring(0, prefixLength)); + sb.append(replacement); + sb.append(s.substring(s.length() - suffixLength)); + return sb.toString(); + } + return s; + } + + /** + * Create an instance of CrashInfo initialized from a Parcel. + */ + public CrashInfo(Parcel in) { + exceptionClassName = in.readString(); + exceptionMessage = in.readString(); + throwFileName = in.readString(); + throwClassName = in.readString(); + throwMethodName = in.readString(); + throwLineNumber = in.readInt(); + stackTrace = in.readString(); + crashTag = in.readString(); + } + + /** + * Save a CrashInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + int start = dest.dataPosition(); + dest.writeString(exceptionClassName); + dest.writeString(exceptionMessage); + dest.writeString(throwFileName); + dest.writeString(throwClassName); + dest.writeString(throwMethodName); + dest.writeInt(throwLineNumber); + dest.writeString(stackTrace); + dest.writeString(crashTag); + int total = dest.dataPosition()-start; + if (Binder.CHECK_PARCEL_SIZE && total > 20*1024) { + Slog.d("Error", "ERR: exClass=" + exceptionClassName); + Slog.d("Error", "ERR: exMsg=" + exceptionMessage); + Slog.d("Error", "ERR: file=" + throwFileName); + Slog.d("Error", "ERR: class=" + throwClassName); + Slog.d("Error", "ERR: method=" + throwMethodName + " line=" + throwLineNumber); + Slog.d("Error", "ERR: stack=" + stackTrace); + Slog.d("Error", "ERR: TOTAL BYTES WRITTEN: " + (dest.dataPosition()-start)); + } + } + + /** + * Dump a CrashInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "exceptionClassName: " + exceptionClassName); + pw.println(prefix + "exceptionMessage: " + exceptionMessage); + pw.println(prefix + "throwFileName: " + throwFileName); + pw.println(prefix + "throwClassName: " + throwClassName); + pw.println(prefix + "throwMethodName: " + throwMethodName); + pw.println(prefix + "throwLineNumber: " + throwLineNumber); + pw.println(prefix + "stackTrace: " + stackTrace); + } + } + + /** + * Parcelable version of {@link CrashInfo} + * + * @hide + */ + public static class ParcelableCrashInfo extends CrashInfo implements Parcelable { + /** + * Create an uninitialized instance of CrashInfo. + */ + public ParcelableCrashInfo() { + } + + /** + * Create an instance of CrashInfo initialized from an exception. + */ + public ParcelableCrashInfo(Throwable tr) { + super(tr); + } + + public ParcelableCrashInfo(Parcel in) { + super(in); + } + + public int describeContents() { + return 0; + } + + public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCrashInfo> CREATOR = + new Parcelable.Creator<ParcelableCrashInfo>() { + @Override + public ParcelableCrashInfo createFromParcel(Parcel in) { + return new ParcelableCrashInfo(in); + } + + @Override + public ParcelableCrashInfo[] newArray(int size) { + return new ParcelableCrashInfo[size]; + } + }; + } + + /** + * Describes an application not responding error. + */ + public static class AnrInfo { + /** + * Activity name. + */ + public String activity; + + /** + * Description of the operation that timed out. + */ + public String cause; + + /** + * Additional info, including CPU stats. + */ + public String info; + + /** + * Create an uninitialized instance of AnrInfo. + */ + public AnrInfo() { + } + + /** + * Create an instance of AnrInfo initialized from a Parcel. + */ + public AnrInfo(Parcel in) { + activity = in.readString(); + cause = in.readString(); + info = in.readString(); + } + + /** + * Save an AnrInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(activity); + dest.writeString(cause); + dest.writeString(info); + } + + /** + * Dump an AnrInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "activity: " + activity); + pw.println(prefix + "cause: " + cause); + pw.println(prefix + "info: " + info); + } + } + + /** + * Describes a battery usage report. + */ + public static class BatteryInfo { + /** + * Percentage of the battery that was used up by the process. + */ + public int usagePercent; + + /** + * Duration in microseconds over which the process used the above + * percentage of battery. + */ + public long durationMicros; + + /** + * Dump of various info impacting battery use. + */ + public String usageDetails; + + /** + * Checkin details. + */ + public String checkinDetails; + + /** + * Create an uninitialized instance of BatteryInfo. + */ + public BatteryInfo() { + } + + /** + * Create an instance of BatteryInfo initialized from a Parcel. + */ + public BatteryInfo(Parcel in) { + usagePercent = in.readInt(); + durationMicros = in.readLong(); + usageDetails = in.readString(); + checkinDetails = in.readString(); + } + + /** + * Save a BatteryInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(usagePercent); + dest.writeLong(durationMicros); + dest.writeString(usageDetails); + dest.writeString(checkinDetails); + } + + /** + * Dump a BatteryInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "usagePercent: " + usagePercent); + pw.println(prefix + "durationMicros: " + durationMicros); + pw.println(prefix + "usageDetails: " + usageDetails); + pw.println(prefix + "checkinDetails: " + checkinDetails); + } + } + + /** + * Describes a running service report. + */ + public static class RunningServiceInfo { + /** + * Duration in milliseconds that the service has been running. + */ + public long durationMillis; + + /** + * Dump of debug information about the service. + */ + public String serviceDetails; + + /** + * Create an uninitialized instance of RunningServiceInfo. + */ + public RunningServiceInfo() { + } + + /** + * Create an instance of RunningServiceInfo initialized from a Parcel. + */ + public RunningServiceInfo(Parcel in) { + durationMillis = in.readLong(); + serviceDetails = in.readString(); + } + + /** + * Save a RunningServiceInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(durationMillis); + dest.writeString(serviceDetails); + } + + /** + * Dump a BatteryInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "durationMillis: " + durationMillis); + pw.println(prefix + "serviceDetails: " + serviceDetails); + } + } + + public static final @android.annotation.NonNull Parcelable.Creator<ApplicationErrorReport> CREATOR + = new Parcelable.Creator<ApplicationErrorReport>() { + public ApplicationErrorReport createFromParcel(Parcel source) { + return new ApplicationErrorReport(source); + } + + public ApplicationErrorReport[] newArray(int size) { + return new ApplicationErrorReport[size]; + } + }; + + public int describeContents() { + return 0; + } + + /** + * Dump the report to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "type: " + type); + pw.println(prefix + "packageName: " + packageName); + pw.println(prefix + "installerPackageName: " + installerPackageName); + pw.println(prefix + "processName: " + processName); + pw.println(prefix + "time: " + time); + pw.println(prefix + "systemApp: " + systemApp); + + switch (type) { + case TYPE_CRASH: + crashInfo.dump(pw, prefix); + break; + case TYPE_ANR: + anrInfo.dump(pw, prefix); + break; + case TYPE_BATTERY: + batteryInfo.dump(pw, prefix); + break; + case TYPE_RUNNING_SERVICE: + runningServiceInfo.dump(pw, prefix); + break; + } + } +}
diff --git a/android/app/ApplicationLoaders.java b/android/app/ApplicationLoaders.java new file mode 100644 index 0000000..2e59b90 --- /dev/null +++ b/android/app/ApplicationLoaders.java
@@ -0,0 +1,302 @@ +/* + * 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.annotation.UnsupportedAppUsage; +import android.content.pm.SharedLibraryInfo; +import android.os.Build; +import android.os.GraphicsEnvironment; +import android.os.Trace; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.os.ClassLoaderFactory; + +import dalvik.system.PathClassLoader; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** @hide */ +public class ApplicationLoaders { + private static final String TAG = "ApplicationLoaders"; + + @UnsupportedAppUsage + public static ApplicationLoaders getDefault() { + return gApplicationLoaders; + } + + ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, + String librarySearchPath, String libraryPermittedPath, + ClassLoader parent, String classLoaderName) { + return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, + librarySearchPath, libraryPermittedPath, parent, classLoaderName, + null); + } + + ClassLoader getClassLoaderWithSharedLibraries( + String zip, int targetSdkVersion, boolean isBundled, + String librarySearchPath, String libraryPermittedPath, + ClassLoader parent, String classLoaderName, + List<ClassLoader> sharedLibraries) { + // For normal usage the cache key used is the same as the zip path. + return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath, + libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries); + } + + /** + * Gets a class loader for a shared library. Additional dependent shared libraries are allowed + * to be specified (sharedLibraries). + * + * Additionally, as an optimization, this will return a pre-created ClassLoader if one has + * been cached by createAndCacheNonBootclasspathSystemClassLoaders. + */ + ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion, + boolean isBundled, String librarySearchPath, String libraryPermittedPath, + ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries) { + ClassLoader loader = getCachedNonBootclasspathSystemLib(zip, parent, classLoaderName, + sharedLibraries); + if (loader != null) { + return loader; + } + + return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, + librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries); + } + + private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, + String librarySearchPath, String libraryPermittedPath, + ClassLoader parent, String cacheKey, + String classLoaderName, List<ClassLoader> sharedLibraries) { + /* + * This is the parent we use if they pass "null" in. In theory + * this should be the "system" class loader; in practice we + * don't use that and can happily (and more efficiently) use the + * bootstrap class loader. + */ + ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent(); + + synchronized (mLoaders) { + if (parent == null) { + parent = baseParent; + } + + /* + * If we're one step up from the base class loader, find + * something in our cache. Otherwise, we create a whole + * new ClassLoader for the zip archive. + */ + if (parent == baseParent) { + ClassLoader loader = mLoaders.get(cacheKey); + if (loader != null) { + return loader; + } + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); + + ClassLoader classloader = ClassLoaderFactory.createClassLoader( + zip, librarySearchPath, libraryPermittedPath, parent, + targetSdkVersion, isBundled, classLoaderName, sharedLibraries); + + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setLayerPaths"); + GraphicsEnvironment.getInstance().setLayerPaths( + classloader, librarySearchPath, libraryPermittedPath); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + if (cacheKey != null) { + mLoaders.put(cacheKey, classloader); + } + return classloader; + } + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); + ClassLoader loader = ClassLoaderFactory.createClassLoader( + zip, null, parent, classLoaderName, sharedLibraries); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + return loader; + } + } + + /** + * Caches system library class loaders which are not on the bootclasspath but are still used + * by many system apps. + * + * All libraries in the closure of libraries to be loaded must be in libs. A library can + * only depend on libraries that come before it in the list. + */ + public void createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs) { + if (mSystemLibsCacheMap != null) { + throw new IllegalStateException("Already cached."); + } + + mSystemLibsCacheMap = new HashMap<String, CachedClassLoader>(); + + for (SharedLibraryInfo lib : libs) { + createAndCacheNonBootclasspathSystemClassLoader(lib); + } + } + + /** + * Caches a single non-bootclasspath class loader. + * + * All of this library's dependencies must have previously been cached. Otherwise, an exception + * is thrown. + */ + private void createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo lib) { + String path = lib.getPath(); + List<SharedLibraryInfo> dependencies = lib.getDependencies(); + + // get cached classloaders for dependencies + ArrayList<ClassLoader> sharedLibraries = null; + if (dependencies != null) { + sharedLibraries = new ArrayList<ClassLoader>(dependencies.size()); + for (SharedLibraryInfo dependency : dependencies) { + String dependencyPath = dependency.getPath(); + CachedClassLoader cached = mSystemLibsCacheMap.get(dependencyPath); + + if (cached == null) { + throw new IllegalStateException("Failed to find dependency " + dependencyPath + + " of cachedlibrary " + path); + } + + sharedLibraries.add(cached.loader); + } + } + + // assume cached libraries work with current sdk since they are built-in + ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/, + null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/, + null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/); + + if (classLoader == null) { + // bad configuration or break in classloading code + throw new IllegalStateException("Failed to cache " + path); + } + + CachedClassLoader cached = new CachedClassLoader(); + cached.loader = classLoader; + cached.sharedLibraries = sharedLibraries; + + Log.d(TAG, "Created zygote-cached class loader: " + path); + mSystemLibsCacheMap.put(path, cached); + } + + private static boolean sharedLibrariesEquals(List<ClassLoader> lhs, List<ClassLoader> rhs) { + if (lhs == null) { + return rhs == null; + } + + return lhs.equals(rhs); + } + + /** + * Returns lib cached with createAndCacheNonBootclasspathSystemClassLoader. This is called by + * the zygote during caching. + * + * If there is an error or the cache is not available, this returns null. + */ + public ClassLoader getCachedNonBootclasspathSystemLib(String zip, ClassLoader parent, + String classLoaderName, List<ClassLoader> sharedLibraries) { + if (mSystemLibsCacheMap == null) { + return null; + } + + // we only cache top-level libs with the default class loader + if (parent != null || classLoaderName != null) { + return null; + } + + CachedClassLoader cached = mSystemLibsCacheMap.get(zip); + if (cached == null) { + return null; + } + + // cached must be built and loaded in the same environment + if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) { + Log.w(TAG, "Unexpected environment for cached library: (" + sharedLibraries + "|" + + cached.sharedLibraries + ")"); + return null; + } + + Log.d(TAG, "Returning zygote-cached class loader: " + zip); + return cached.loader; + } + + /** + * Creates a classloader for the WebView APK and places it in the cache of loaders maintained + * by this class. This is used in the WebView zygote, where its presence in the cache speeds up + * startup and enables memory sharing. + */ + public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath, + String cacheKey) { + // The correct paths are calculated by WebViewZygote in the system server and passed to + // us here. We hardcode the other parameters: WebView always targets the current SDK, + // does not need to use non-public system libraries, and uses the base classloader as its + // parent to permit usage of the cache. + // The cache key is passed separately to enable the stub WebView to be cached under the + // stub's APK path, when the actual package path is the donor APK. + return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null, + cacheKey, null /* classLoaderName */, null /* sharedLibraries */); + } + + /** + * Adds a new path the classpath of the given loader. + * @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}. + */ + void addPath(ClassLoader classLoader, String dexPath) { + if (!(classLoader instanceof PathClassLoader)) { + throw new IllegalStateException("class loader is not a PathClassLoader"); + } + final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader; + baseDexClassLoader.addDexPath(dexPath); + } + + /** + * @hide + */ + void addNative(ClassLoader classLoader, Collection<String> libPaths) { + if (!(classLoader instanceof PathClassLoader)) { + throw new IllegalStateException("class loader is not a PathClassLoader"); + } + final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader; + baseDexClassLoader.addNativePath(libPaths); + } + + @UnsupportedAppUsage + private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<>(); + + private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders(); + + private static class CachedClassLoader { + ClassLoader loader; + + /** + * The shared libraries used when constructing loader for verification. + */ + List<ClassLoader> sharedLibraries; + } + + /** + * This is a map of zip to associated class loader. + */ + private Map<String, CachedClassLoader> mSystemLibsCacheMap = null; +}
diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java new file mode 100644 index 0000000..360be35 --- /dev/null +++ b/android/app/ApplicationPackageManager.java
@@ -0,0 +1,3157 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.annotation.StringRes; +import android.annotation.UnsupportedAppUsage; +import android.annotation.UserIdInt; +import android.annotation.XmlRes; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ChangedPackages; +import android.content.pm.ComponentInfo; +import android.content.pm.FeatureInfo; +import android.content.pm.IOnPermissionsChangeListener; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageManager; +import android.content.pm.IPackageMoveObserver; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstantAppInfo; +import android.content.pm.InstrumentationInfo; +import android.content.pm.IntentFilterVerificationInfo; +import android.content.pm.KeySet; +import android.content.pm.ModuleInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.SharedLibraryInfo; +import android.content.pm.SuspendDialogInfo; +import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.VersionedPackage; +import android.content.pm.dex.ArtManager; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.StrictMode; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.provider.Settings; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructStat; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IconDrawableFactory; +import android.util.LauncherIcons; +import android.util.Log; +import android.view.Display; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.SomeArgs; +import com.android.internal.util.Preconditions; +import com.android.internal.util.UserIcons; + +import dalvik.system.VMRuntime; + +import libcore.util.EmptyArray; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** @hide */ +public class ApplicationPackageManager extends PackageManager { + private static final String TAG = "ApplicationPackageManager"; + private final static boolean DEBUG_ICONS = false; + + private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB + + // Default flags to use with PackageManager when no flags are given. + private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private UserManager mUserManager; + @GuardedBy("mLock") + private PackageInstaller mInstaller; + @GuardedBy("mLock") + private ArtManager mArtManager; + + @GuardedBy("mDelegates") + private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>(); + + @GuardedBy("mLock") + private String mPermissionsControllerPackageName; + + UserManager getUserManager() { + synchronized (mLock) { + if (mUserManager == null) { + mUserManager = UserManager.get(mContext); + } + return mUserManager; + } + } + + @Override + public int getUserId() { + return mContext.getUserId(); + } + + @Override + public PackageInfo getPackageInfo(String packageName, int flags) + throws NameNotFoundException { + return getPackageInfoAsUser(packageName, flags, getUserId()); + } + + @Override + public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags) + throws NameNotFoundException { + final int userId = getUserId(); + try { + PackageInfo pi = mPM.getPackageInfoVersioned(versionedPackage, + updateFlagsForPackage(flags, userId), userId); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + throw new NameNotFoundException(versionedPackage.toString()); + } + + @Override + public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) + throws NameNotFoundException { + try { + PackageInfo pi = mPM.getPackageInfo(packageName, + updateFlagsForPackage(flags, userId), userId); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + throw new NameNotFoundException(packageName); + } + + @Override + public String[] currentToCanonicalPackageNames(String[] names) { + try { + return mPM.currentToCanonicalPackageNames(names); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String[] canonicalToCurrentPackageNames(String[] names) { + try { + return mPM.canonicalToCurrentPackageNames(names); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public Intent getLaunchIntentForPackage(String packageName) { + // First see if the package has an INFO activity; the existence of + // such an activity is implied to be the desired front-door for the + // overall package (such as if it has multiple launcher entries). + Intent intentToResolve = new Intent(Intent.ACTION_MAIN); + intentToResolve.addCategory(Intent.CATEGORY_INFO); + intentToResolve.setPackage(packageName); + List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0); + + // Otherwise, try to find a main launcher activity. + if (ris == null || ris.size() <= 0) { + // reuse the intent instance + intentToResolve.removeCategory(Intent.CATEGORY_INFO); + intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); + intentToResolve.setPackage(packageName); + ris = queryIntentActivities(intentToResolve, 0); + } + if (ris == null || ris.size() <= 0) { + return null; + } + Intent intent = new Intent(intentToResolve); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName(ris.get(0).activityInfo.packageName, + ris.get(0).activityInfo.name); + return intent; + } + + @Override + public Intent getLeanbackLaunchIntentForPackage(String packageName) { + return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_LEANBACK_LAUNCHER); + } + + @Override + public Intent getCarLaunchIntentForPackage(String packageName) { + return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_CAR_LAUNCHER); + } + + private Intent getLaunchIntentForPackageAndCategory(String packageName, String category) { + // Try to find a main launcher activity for the given categories. + Intent intentToResolve = new Intent(Intent.ACTION_MAIN); + intentToResolve.addCategory(category); + intentToResolve.setPackage(packageName); + List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0); + + if (ris == null || ris.size() <= 0) { + return null; + } + Intent intent = new Intent(intentToResolve); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName(ris.get(0).activityInfo.packageName, + ris.get(0).activityInfo.name); + return intent; + } + + @Override + public int[] getPackageGids(String packageName) throws NameNotFoundException { + return getPackageGids(packageName, 0); + } + + @Override + public int[] getPackageGids(String packageName, int flags) + throws NameNotFoundException { + final int userId = getUserId(); + try { + int[] gids = mPM.getPackageGids(packageName, + updateFlagsForPackage(flags, userId), userId); + if (gids != null) { + return gids; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(packageName); + } + + @Override + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + return getPackageUidAsUser(packageName, flags, getUserId()); + } + + @Override + public int getPackageUidAsUser(String packageName, int userId) throws NameNotFoundException { + return getPackageUidAsUser(packageName, 0, userId); + } + + @Override + public int getPackageUidAsUser(String packageName, int flags, int userId) + throws NameNotFoundException { + try { + int uid = mPM.getPackageUid(packageName, + updateFlagsForPackage(flags, userId), userId); + if (uid >= 0) { + return uid; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(packageName); + } + + @Override + public PermissionInfo getPermissionInfo(String name, int flags) + throws NameNotFoundException { + try { + PermissionInfo pi = mPM.getPermissionInfo(name, + mContext.getOpPackageName(), flags); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(name); + } + + @Override + @SuppressWarnings("unchecked") + public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) + throws NameNotFoundException { + try { + ParceledListSlice<PermissionInfo> parceledList = + mPM.queryPermissionsByGroup(group, flags); + if (parceledList != null) { + List<PermissionInfo> pi = parceledList.getList(); + if (pi != null) { + return pi; + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(group); + } + + @Override + public boolean arePermissionsIndividuallyControlled() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_permissionsIndividuallyControlled); + } + + @Override + public boolean isWirelessConsentModeEnabled() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_wirelessConsentRequired); + } + + @Override + public PermissionGroupInfo getPermissionGroupInfo(String name, + int flags) throws NameNotFoundException { + try { + PermissionGroupInfo pgi = mPM.getPermissionGroupInfo(name, flags); + if (pgi != null) { + return pgi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(name); + } + + @Override + @SuppressWarnings("unchecked") + public List<PermissionGroupInfo> getAllPermissionGroups(int flags) { + try { + ParceledListSlice<PermissionGroupInfo> parceledList = + mPM.getAllPermissionGroups(flags); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags) + throws NameNotFoundException { + return getApplicationInfoAsUser(packageName, flags, getUserId()); + } + + @Override + public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId) + throws NameNotFoundException { + try { + ApplicationInfo ai = mPM.getApplicationInfo(packageName, + updateFlagsForApplication(flags, userId), userId); + if (ai != null) { + // This is a temporary hack. Callers must use + // createPackageContext(packageName).getApplicationInfo() to + // get the right paths. + return maybeAdjustApplicationInfo(ai); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(packageName); + } + + private static ApplicationInfo maybeAdjustApplicationInfo(ApplicationInfo info) { + // 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. + if (info.primaryCpuAbi != null && info.secondaryCpuAbi != null) { + final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet(); + + // Get the instruction set that the libraries of secondary Abi is supported. + // In presence of a native bridge this might be different than the one secondary Abi used. + String secondaryIsa = VMRuntime.getInstructionSet(info.secondaryCpuAbi); + final String secondaryDexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa); + secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa; + + // If the runtimeIsa is the same as the primary isa, then we do nothing. + // Everything will be set up correctly because info.nativeLibraryDir will + // correspond to the right ISA. + if (runtimeIsa.equals(secondaryIsa)) { + ApplicationInfo modified = new ApplicationInfo(info); + modified.nativeLibraryDir = info.secondaryNativeLibraryDir; + return modified; + } + } + return info; + } + + @Override + public ActivityInfo getActivityInfo(ComponentName className, int flags) + throws NameNotFoundException { + final int userId = getUserId(); + try { + ActivityInfo ai = mPM.getActivityInfo(className, + updateFlagsForComponent(flags, userId, null), userId); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName className, int flags) + throws NameNotFoundException { + final int userId = getUserId(); + try { + ActivityInfo ai = mPM.getReceiverInfo(className, + updateFlagsForComponent(flags, userId, null), userId); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public ServiceInfo getServiceInfo(ComponentName className, int flags) + throws NameNotFoundException { + final int userId = getUserId(); + try { + ServiceInfo si = mPM.getServiceInfo(className, + updateFlagsForComponent(flags, userId, null), userId); + if (si != null) { + return si; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public ProviderInfo getProviderInfo(ComponentName className, int flags) + throws NameNotFoundException { + final int userId = getUserId(); + try { + ProviderInfo pi = mPM.getProviderInfo(className, + updateFlagsForComponent(flags, userId, null), userId); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public String[] getSystemSharedLibraryNames() { + try { + return mPM.getSystemSharedLibraryNames(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags) { + return getSharedLibrariesAsUser(flags, getUserId()); + } + + /** @hide */ + @Override + @SuppressWarnings("unchecked") + public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) { + try { + ParceledListSlice<SharedLibraryInfo> sharedLibs = mPM.getSharedLibraries( + mContext.getOpPackageName(), flags, userId); + if (sharedLibs == null) { + return Collections.emptyList(); + } + return sharedLibs.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @NonNull + @Override + public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName, + @InstallFlags int flags) { + try { + ParceledListSlice<SharedLibraryInfo> sharedLibraries = mPM.getDeclaredSharedLibraries( + packageName, flags, mContext.getUserId()); + return sharedLibraries != null ? sharedLibraries.getList() : Collections.emptyList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public @NonNull String getServicesSystemSharedLibraryPackageName() { + try { + return mPM.getServicesSystemSharedLibraryPackageName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public @NonNull String getSharedSystemSharedLibraryPackageName() { + try { + return mPM.getSharedSystemSharedLibraryPackageName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public ChangedPackages getChangedPackages(int sequenceNumber) { + try { + return mPM.getChangedPackages(sequenceNumber, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @SuppressWarnings("unchecked") + public FeatureInfo[] getSystemAvailableFeatures() { + try { + ParceledListSlice<FeatureInfo> parceledList = + mPM.getSystemAvailableFeatures(); + if (parceledList == null) { + return new FeatureInfo[0]; + } + final List<FeatureInfo> list = parceledList.getList(); + final FeatureInfo[] res = new FeatureInfo[list.size()]; + for (int i = 0; i < res.length; i++) { + res[i] = list.get(i); + } + return res; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean hasSystemFeature(String name) { + return hasSystemFeature(name, 0); + } + + @Override + public boolean hasSystemFeature(String name, int version) { + try { + return mPM.hasSystemFeature(name, version); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int checkPermission(String permName, String pkgName) { + try { + return mPM.checkPermission(permName, pkgName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean isPermissionRevokedByPolicy(String permName, String pkgName) { + try { + return mPM.isPermissionRevokedByPolicy(permName, pkgName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @Override + public String getPermissionControllerPackageName() { + synchronized (mLock) { + if (mPermissionsControllerPackageName == null) { + try { + mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mPermissionsControllerPackageName; + } + } + + @Override + public boolean addPermission(PermissionInfo info) { + try { + return mPM.addPermission(info); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean addPermissionAsync(PermissionInfo info) { + try { + return mPM.addPermissionAsync(info); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void removePermission(String name) { + try { + mPM.removePermission(name); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void grantRuntimePermission(String packageName, String permissionName, + UserHandle user) { + try { + mPM.grantRuntimePermission(packageName, permissionName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void revokeRuntimePermission(String packageName, String permissionName, + UserHandle user) { + try { + mPM.revokeRuntimePermission(packageName, permissionName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getPermissionFlags(String permissionName, String packageName, UserHandle user) { + try { + return mPM.getPermissionFlags(permissionName, packageName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void updatePermissionFlags(String permissionName, String packageName, + int flagMask, int flagValues, UserHandle user) { + try { + mPM.updatePermissionFlags(permissionName, packageName, flagMask, + flagValues, + mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q, + user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public @NonNull Set<String> getWhitelistedRestrictedPermissions( + @NonNull String packageName, @PermissionWhitelistFlags int whitelistFlags) { + try { + final List<String> whitelist = mPM.getWhitelistedRestrictedPermissions( + packageName, whitelistFlags, getUserId()); + if (whitelist != null) { + return new ArraySet<>(whitelist); + } + return Collections.emptySet(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean addWhitelistedRestrictedPermission(@NonNull String packageName, + @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags) { + try { + return mPM.addWhitelistedRestrictedPermission(packageName, permission, + whitelistFlags, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName, + @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags) { + try { + return mPM.removeWhitelistedRestrictedPermission(packageName, permission, + whitelistFlags, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @UnsupportedAppUsage + public boolean shouldShowRequestPermissionRationale(String permission) { + try { + return mPM.shouldShowRequestPermissionRationale(permission, + mContext.getPackageName(), getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int checkSignatures(String pkg1, String pkg2) { + try { + return mPM.checkSignatures(pkg1, pkg2); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int checkSignatures(int uid1, int uid2) { + try { + return mPM.checkUidSignatures(uid1, uid2); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean hasSigningCertificate( + String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) { + try { + return mPM.hasSigningCertificate(packageName, certificate, type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean hasSigningCertificate( + int uid, byte[] certificate, @PackageManager.CertificateInputType int type) { + try { + return mPM.hasUidSigningCertificate(uid, certificate, type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String[] getPackagesForUid(int uid) { + try { + return mPM.getPackagesForUid(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String getNameForUid(int uid) { + try { + return mPM.getNameForUid(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String[] getNamesForUids(int[] uids) { + try { + return mPM.getNamesForUids(uids); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getUidForSharedUser(String sharedUserName) + throws NameNotFoundException { + try { + int uid = mPM.getUidForSharedUser(sharedUserName); + if(uid != -1) { + return uid; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + throw new NameNotFoundException("No shared userid for user:"+sharedUserName); + } + + @Override + public List<ModuleInfo> getInstalledModules(int flags) { + try { + return mPM.getInstalledModules(flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException { + try { + ModuleInfo mi = mPM.getModuleInfo(packageName, flags); + if (mi != null) { + return mi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException("No module info for package: " + packageName); + } + + @SuppressWarnings("unchecked") + @Override + public List<PackageInfo> getInstalledPackages(int flags) { + return getInstalledPackagesAsUser(flags, getUserId()); + } + + /** @hide */ + @Override + @SuppressWarnings("unchecked") + public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) { + try { + ParceledListSlice<PackageInfo> parceledList = + mPM.getInstalledPackages(updateFlagsForPackage(flags, userId), userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @SuppressWarnings("unchecked") + @Override + public List<PackageInfo> getPackagesHoldingPermissions( + String[] permissions, int flags) { + final int userId = getUserId(); + try { + ParceledListSlice<PackageInfo> parceledList = + mPM.getPackagesHoldingPermissions(permissions, + updateFlagsForPackage(flags, userId), userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @SuppressWarnings("unchecked") + @Override + public List<ApplicationInfo> getInstalledApplications(int flags) { + return getInstalledApplicationsAsUser(flags, getUserId()); + } + + /** @hide */ + @SuppressWarnings("unchecked") + @Override + public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) { + try { + ParceledListSlice<ApplicationInfo> parceledList = + mPM.getInstalledApplications(updateFlagsForApplication(flags, userId), userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @SuppressWarnings("unchecked") + @Override + public List<InstantAppInfo> getInstantApps() { + try { + ParceledListSlice<InstantAppInfo> slice = mPM.getInstantApps(getUserId()); + if (slice != null) { + return slice.getList(); + } + return Collections.emptyList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public Drawable getInstantAppIcon(String packageName) { + try { + Bitmap bitmap = mPM.getInstantAppIcon(packageName, getUserId()); + if (bitmap != null) { + return new BitmapDrawable(null, bitmap); + } + return null; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean isInstantApp() { + return isInstantApp(mContext.getPackageName()); + } + + @Override + public boolean isInstantApp(String packageName) { + try { + return mPM.isInstantApp(packageName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public int getInstantAppCookieMaxBytes() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES, + DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES); + } + + @Override + public int getInstantAppCookieMaxSize() { + return getInstantAppCookieMaxBytes(); + } + + @Override + public @NonNull byte[] getInstantAppCookie() { + try { + final byte[] cookie = mPM.getInstantAppCookie(mContext.getPackageName(), getUserId()); + if (cookie != null) { + return cookie; + } else { + return EmptyArray.BYTE; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void clearInstantAppCookie() { + updateInstantAppCookie(null); + } + + @Override + public void updateInstantAppCookie(@NonNull byte[] cookie) { + if (cookie != null && cookie.length > getInstantAppCookieMaxBytes()) { + throw new IllegalArgumentException("instant cookie longer than " + + getInstantAppCookieMaxBytes()); + } + try { + mPM.setInstantAppCookie(mContext.getPackageName(), cookie, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @UnsupportedAppUsage + @Override + public boolean setInstantAppCookie(@NonNull byte[] cookie) { + try { + return mPM.setInstantAppCookie(mContext.getPackageName(), cookie, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public ResolveInfo resolveActivity(Intent intent, int flags) { + return resolveActivityAsUser(intent, flags, getUserId()); + } + + @Override + public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + try { + return mPM.resolveIntent( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + updateFlagsForComponent(flags, userId, intent), + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public List<ResolveInfo> queryIntentActivities(Intent intent, + int flags) { + return queryIntentActivitiesAsUser(intent, flags, getUserId()); + } + + /** @hide Same as above but for a specific user */ + @Override + @SuppressWarnings("unchecked") + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, + int flags, int userId) { + try { + ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentActivities( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + updateFlagsForComponent(flags, userId, intent), + userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @SuppressWarnings("unchecked") + public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics, + Intent intent, int flags) { + final int userId = getUserId(); + final ContentResolver resolver = mContext.getContentResolver(); + + String[] specificTypes = null; + if (specifics != null) { + final int N = specifics.length; + for (int i=0; i<N; i++) { + Intent sp = specifics[i]; + if (sp != null) { + String t = sp.resolveTypeIfNeeded(resolver); + if (t != null) { + if (specificTypes == null) { + specificTypes = new String[N]; + } + specificTypes[i] = t; + } + } + } + } + + try { + ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentActivityOptions( + caller, + specifics, + specificTypes, + intent, + intent.resolveTypeIfNeeded(resolver), + updateFlagsForComponent(flags, userId, intent), + userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @Override + @SuppressWarnings("unchecked") + public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) { + try { + ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentReceivers( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + updateFlagsForComponent(flags, userId, intent), + userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { + return queryBroadcastReceiversAsUser(intent, flags, getUserId()); + } + + @Override + public ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags, + @UserIdInt int userId) { + try { + return mPM.resolveService( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + updateFlagsForComponent(flags, userId, intent), + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public ResolveInfo resolveService(Intent intent, int flags) { + return resolveServiceAsUser(intent, flags, getUserId()); + } + + @Override + @SuppressWarnings("unchecked") + public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { + try { + ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentServices( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + updateFlagsForComponent(flags, userId, intent), + userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + return queryIntentServicesAsUser(intent, flags, getUserId()); + } + + @Override + @SuppressWarnings("unchecked") + public List<ResolveInfo> queryIntentContentProvidersAsUser( + Intent intent, int flags, int userId) { + try { + ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentContentProviders( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + updateFlagsForComponent(flags, userId, intent), + userId); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) { + return queryIntentContentProvidersAsUser(intent, flags, getUserId()); + } + + @Override + public ProviderInfo resolveContentProvider(String name, int flags) { + return resolveContentProviderAsUser(name, flags, getUserId()); + } + + /** @hide **/ + @Override + public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) { + try { + return mPM.resolveContentProvider(name, + updateFlagsForComponent(flags, userId, null), userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public List<ProviderInfo> queryContentProviders(String processName, + int uid, int flags) { + return queryContentProviders(processName, uid, flags, null); + } + + @Override + @SuppressWarnings("unchecked") + public List<ProviderInfo> queryContentProviders(String processName, + int uid, int flags, String metaDataKey) { + try { + ParceledListSlice<ProviderInfo> slice = mPM.queryContentProviders(processName, uid, + updateFlagsForComponent(flags, UserHandle.getUserId(uid), null), metaDataKey); + return slice != null ? slice.getList() : Collections.<ProviderInfo>emptyList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public InstrumentationInfo getInstrumentationInfo( + ComponentName className, int flags) + throws NameNotFoundException { + try { + InstrumentationInfo ii = mPM.getInstrumentationInfo( + className, flags); + if (ii != null) { + return ii; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + @SuppressWarnings("unchecked") + public List<InstrumentationInfo> queryInstrumentation( + String targetPackage, int flags) { + try { + ParceledListSlice<InstrumentationInfo> parceledList = + mPM.queryInstrumentation(targetPackage, flags); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Nullable + @Override + public Drawable getDrawable(String packageName, @DrawableRes int resId, + @Nullable ApplicationInfo appInfo) { + final ResourceName name = new ResourceName(packageName, resId); + final Drawable cachedIcon = getCachedIcon(name); + if (cachedIcon != null) { + return cachedIcon; + } + + if (appInfo == null) { + try { + appInfo = getApplicationInfo(packageName, sDefaultFlags); + } catch (NameNotFoundException e) { + return null; + } + } + + if (resId != 0) { + try { + final Resources r = getResourcesForApplication(appInfo); + final Drawable dr = r.getDrawable(resId, null); + if (dr != null) { + putCachedIcon(name, dr); + } + + if (false) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId) + + " from package " + packageName + + ": app scale=" + r.getCompatibilityInfo().applicationScale + + ", caller scale=" + mContext.getResources() + .getCompatibilityInfo().applicationScale, + e); + } + if (DEBUG_ICONS) { + Log.v(TAG, "Getting drawable 0x" + + Integer.toHexString(resId) + " from " + r + + ": " + dr); + } + return dr; + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for " + + appInfo.packageName); + } catch (Resources.NotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for " + + appInfo.packageName + ": " + e.getMessage()); + } catch (Exception e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving icon 0x" + + Integer.toHexString(resId) + " in package " + + packageName, e); + } + } + + return null; + } + + @Override public Drawable getActivityIcon(ComponentName activityName) + throws NameNotFoundException { + return getActivityInfo(activityName, sDefaultFlags).loadIcon(this); + } + + @Override public Drawable getActivityIcon(Intent intent) + throws NameNotFoundException { + if (intent.getComponent() != null) { + return getActivityIcon(intent.getComponent()); + } + + ResolveInfo info = resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return info.activityInfo.loadIcon(this); + } + + throw new NameNotFoundException(intent.toUri(0)); + } + + @Override public Drawable getDefaultActivityIcon() { + return mContext.getDrawable(com.android.internal.R.drawable.sym_def_app_icon); + } + + @Override public Drawable getApplicationIcon(ApplicationInfo info) { + return info.loadIcon(this); + } + + @Override public Drawable getApplicationIcon(String packageName) + throws NameNotFoundException { + return getApplicationIcon(getApplicationInfo(packageName, sDefaultFlags)); + } + + @Override + public Drawable getActivityBanner(ComponentName activityName) + throws NameNotFoundException { + return getActivityInfo(activityName, sDefaultFlags).loadBanner(this); + } + + @Override + public Drawable getActivityBanner(Intent intent) + throws NameNotFoundException { + if (intent.getComponent() != null) { + return getActivityBanner(intent.getComponent()); + } + + ResolveInfo info = resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return info.activityInfo.loadBanner(this); + } + + throw new NameNotFoundException(intent.toUri(0)); + } + + @Override + public Drawable getApplicationBanner(ApplicationInfo info) { + return info.loadBanner(this); + } + + @Override + public Drawable getApplicationBanner(String packageName) + throws NameNotFoundException { + return getApplicationBanner(getApplicationInfo(packageName, sDefaultFlags)); + } + + @Override + public Drawable getActivityLogo(ComponentName activityName) + throws NameNotFoundException { + return getActivityInfo(activityName, sDefaultFlags).loadLogo(this); + } + + @Override + public Drawable getActivityLogo(Intent intent) + throws NameNotFoundException { + if (intent.getComponent() != null) { + return getActivityLogo(intent.getComponent()); + } + + ResolveInfo info = resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return info.activityInfo.loadLogo(this); + } + + throw new NameNotFoundException(intent.toUri(0)); + } + + @Override + public Drawable getApplicationLogo(ApplicationInfo info) { + return info.loadLogo(this); + } + + @Override + public Drawable getApplicationLogo(String packageName) + throws NameNotFoundException { + return getApplicationLogo(getApplicationInfo(packageName, sDefaultFlags)); + } + + @Override + public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) { + if (!isManagedProfile(user.getIdentifier())) { + return icon; + } + Drawable badge = new LauncherIcons(mContext).getBadgeDrawable( + com.android.internal.R.drawable.ic_corp_icon_badge_case, + getUserBadgeColor(user)); + return getBadgedDrawable(icon, badge, null, true); + } + + @Override + public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, + Rect badgeLocation, int badgeDensity) { + Drawable badgeDrawable = getUserBadgeForDensity(user, badgeDensity); + if (badgeDrawable == null) { + return drawable; + } + return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true); + } + + @VisibleForTesting + public static final int[] CORP_BADGE_LABEL_RES_ID = new int[] { + com.android.internal.R.string.managed_profile_label_badge, + com.android.internal.R.string.managed_profile_label_badge_2, + com.android.internal.R.string.managed_profile_label_badge_3 + }; + + private int getUserBadgeColor(UserHandle user) { + return IconDrawableFactory.getUserBadgeColor(getUserManager(), user.getIdentifier()); + } + + @Override + public Drawable getUserBadgeForDensity(UserHandle user, int density) { + Drawable badgeColor = getManagedProfileIconForDensity(user, + com.android.internal.R.drawable.ic_corp_badge_color, density); + if (badgeColor == null) { + return null; + } + Drawable badgeForeground = getDrawableForDensity( + com.android.internal.R.drawable.ic_corp_badge_case, density); + badgeForeground.setTint(getUserBadgeColor(user)); + Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground }); + return badge; + } + + @Override + public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) { + Drawable badge = getManagedProfileIconForDensity(user, + com.android.internal.R.drawable.ic_corp_badge_no_background, density); + if (badge != null) { + badge.setTint(getUserBadgeColor(user)); + } + return badge; + } + + private Drawable getDrawableForDensity(int drawableId, int density) { + if (density <= 0) { + density = mContext.getResources().getDisplayMetrics().densityDpi; + } + return mContext.getResources().getDrawableForDensity(drawableId, density); + } + + private Drawable getManagedProfileIconForDensity(UserHandle user, int drawableId, int density) { + if (isManagedProfile(user.getIdentifier())) { + return getDrawableForDensity(drawableId, density); + } + return null; + } + + @Override + public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { + if (isManagedProfile(user.getIdentifier())) { + int badge = getUserManager().getManagedProfileBadge(user.getIdentifier()); + int resourceId = CORP_BADGE_LABEL_RES_ID[badge % CORP_BADGE_LABEL_RES_ID.length]; + return Resources.getSystem().getString(resourceId, label); + } + return label; + } + + @Override + public Resources getResourcesForActivity(ComponentName activityName) + throws NameNotFoundException { + return getResourcesForApplication( + getActivityInfo(activityName, sDefaultFlags).applicationInfo); + } + + @Override + public Resources getResourcesForApplication(@NonNull ApplicationInfo app) + throws NameNotFoundException { + if (app.packageName.equals("system")) { + return mContext.mMainThread.getSystemUiContext().getResources(); + } + final boolean sameUid = (app.uid == Process.myUid()); + final Resources r = mContext.mMainThread.getTopLevelResources( + sameUid ? app.sourceDir : app.publicSourceDir, + sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, + app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, + mContext.mPackageInfo); + if (r != null) { + return r; + } + throw new NameNotFoundException("Unable to open " + app.publicSourceDir); + + } + + @Override + public Resources getResourcesForApplication(String appPackageName) + throws NameNotFoundException { + return getResourcesForApplication( + getApplicationInfo(appPackageName, sDefaultFlags)); + } + + /** @hide */ + @Override + public Resources getResourcesForApplicationAsUser(String appPackageName, int userId) + throws NameNotFoundException { + if (userId < 0) { + throw new IllegalArgumentException( + "Call does not support special user #" + userId); + } + if ("system".equals(appPackageName)) { + return mContext.mMainThread.getSystemUiContext().getResources(); + } + try { + ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, sDefaultFlags, userId); + if (ai != null) { + return getResourcesForApplication(ai); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + throw new NameNotFoundException("Package " + appPackageName + " doesn't exist"); + } + + volatile int mCachedSafeMode = -1; + + @Override + public boolean isSafeMode() { + try { + if (mCachedSafeMode < 0) { + mCachedSafeMode = mPM.isSafeMode() ? 1 : 0; + } + return mCachedSafeMode != 0; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) { + synchronized (mPermissionListeners) { + if (mPermissionListeners.get(listener) != null) { + return; + } + OnPermissionsChangeListenerDelegate delegate = + new OnPermissionsChangeListenerDelegate(listener, Looper.getMainLooper()); + try { + mPM.addOnPermissionsChangeListener(delegate); + mPermissionListeners.put(listener, delegate); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + @Override + public void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener) { + synchronized (mPermissionListeners) { + IOnPermissionsChangeListener delegate = mPermissionListeners.get(listener); + if (delegate != null) { + try { + mPM.removeOnPermissionsChangeListener(delegate); + mPermissionListeners.remove(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + @UnsupportedAppUsage + static void configurationChanged() { + synchronized (sSync) { + sIconCache.clear(); + sStringCache.clear(); + } + } + + @UnsupportedAppUsage + protected ApplicationPackageManager(ContextImpl context, + IPackageManager pm) { + mContext = context; + mPM = pm; + } + + /** + * Update given flags when being used to request {@link PackageInfo}. + */ + private int updateFlagsForPackage(int flags, int userId) { + if ((flags & (GET_ACTIVITIES | GET_RECEIVERS | GET_SERVICES | GET_PROVIDERS)) != 0) { + // Caller is asking for component details, so they'd better be + // asking for specific Direct Boot matching behavior + if ((flags & (MATCH_DIRECT_BOOT_UNAWARE + | MATCH_DIRECT_BOOT_AWARE + | MATCH_DIRECT_BOOT_AUTO)) == 0) { + onImplicitDirectBoot(userId); + } + } + return flags; + } + + /** + * Update given flags when being used to request {@link ApplicationInfo}. + */ + private int updateFlagsForApplication(int flags, int userId) { + return updateFlagsForPackage(flags, userId); + } + + /** + * Update given flags when being used to request {@link ComponentInfo}. + */ + private int updateFlagsForComponent(int flags, int userId, Intent intent) { + if (intent != null) { + if ((intent.getFlags() & Intent.FLAG_DIRECT_BOOT_AUTO) != 0) { + flags |= MATCH_DIRECT_BOOT_AUTO; + } + } + + // Caller is asking for component details, so they'd better be + // asking for specific Direct Boot matching behavior + if ((flags & (MATCH_DIRECT_BOOT_UNAWARE + | MATCH_DIRECT_BOOT_AWARE + | MATCH_DIRECT_BOOT_AUTO)) == 0) { + onImplicitDirectBoot(userId); + } + return flags; + } + + private void onImplicitDirectBoot(int userId) { + // Only report if someone is relying on implicit behavior while the user + // is locked; code running when unlocked is going to see both aware and + // unaware components. + if (StrictMode.vmImplicitDirectBootEnabled()) { + // We can cache the unlocked state for the userId we're running as, + // since any relocking of that user will always result in our + // process being killed to release any CE FDs we're holding onto. + if (userId == UserHandle.myUserId()) { + if (mUserUnlocked) { + return; + } else if (mContext.getSystemService(UserManager.class) + .isUserUnlockingOrUnlocked(userId)) { + mUserUnlocked = true; + } else { + StrictMode.onImplicitDirectBoot(); + } + } else if (!mContext.getSystemService(UserManager.class) + .isUserUnlockingOrUnlocked(userId)) { + StrictMode.onImplicitDirectBoot(); + } + } + } + + @Nullable + private Drawable getCachedIcon(@NonNull ResourceName name) { + synchronized (sSync) { + final WeakReference<Drawable.ConstantState> wr = sIconCache.get(name); + if (DEBUG_ICONS) Log.v(TAG, "Get cached weak drawable ref for " + + name + ": " + wr); + if (wr != null) { // we have the activity + final Drawable.ConstantState state = wr.get(); + if (state != null) { + if (DEBUG_ICONS) { + Log.v(TAG, "Get cached drawable state for " + name + ": " + state); + } + // Note: It's okay here to not use the newDrawable(Resources) variant + // of the API. The ConstantState comes from a drawable that was + // originally created by passing the proper app Resources instance + // which means the state should already contain the proper + // resources specific information (like density.) See + // BitmapDrawable.BitmapState for instance. + return state.newDrawable(); + } + // our entry has been purged + sIconCache.remove(name); + } + } + return null; + } + + private void putCachedIcon(@NonNull ResourceName name, @NonNull Drawable dr) { + synchronized (sSync) { + sIconCache.put(name, new WeakReference<>(dr.getConstantState())); + if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable state for " + name + ": " + dr); + } + } + + static void handlePackageBroadcast(int cmd, String[] pkgList, boolean hasPkgInfo) { + boolean immediateGc = false; + if (cmd == ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE) { + immediateGc = true; + } + if (pkgList != null && (pkgList.length > 0)) { + boolean needCleanup = false; + for (String ssp : pkgList) { + synchronized (sSync) { + for (int i=sIconCache.size()-1; i>=0; i--) { + ResourceName nm = sIconCache.keyAt(i); + if (nm.packageName.equals(ssp)) { + //Log.i(TAG, "Removing cached drawable for " + nm); + sIconCache.removeAt(i); + needCleanup = true; + } + } + for (int i=sStringCache.size()-1; i>=0; i--) { + ResourceName nm = sStringCache.keyAt(i); + if (nm.packageName.equals(ssp)) { + //Log.i(TAG, "Removing cached string for " + nm); + sStringCache.removeAt(i); + needCleanup = true; + } + } + } + } + if (needCleanup || hasPkgInfo) { + if (immediateGc) { + // Schedule an immediate gc. + Runtime.getRuntime().gc(); + } else { + ActivityThread.currentActivityThread().scheduleGcIdler(); + } + } + } + } + + private static final class ResourceName { + final String packageName; + final int iconId; + + ResourceName(String _packageName, int _iconId) { + packageName = _packageName; + iconId = _iconId; + } + + ResourceName(ApplicationInfo aInfo, int _iconId) { + this(aInfo.packageName, _iconId); + } + + ResourceName(ComponentInfo cInfo, int _iconId) { + this(cInfo.applicationInfo.packageName, _iconId); + } + + ResourceName(ResolveInfo rInfo, int _iconId) { + this(rInfo.activityInfo.applicationInfo.packageName, _iconId); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ResourceName that = (ResourceName) o; + + if (iconId != that.iconId) return false; + return !(packageName != null ? + !packageName.equals(that.packageName) : that.packageName != null); + + } + + @Override + public int hashCode() { + int result; + result = packageName.hashCode(); + result = 31 * result + iconId; + return result; + } + + @Override + public String toString() { + return "{ResourceName " + packageName + " / " + iconId + "}"; + } + } + + private CharSequence getCachedString(ResourceName name) { + synchronized (sSync) { + WeakReference<CharSequence> wr = sStringCache.get(name); + if (wr != null) { // we have the activity + CharSequence cs = wr.get(); + if (cs != null) { + return cs; + } + // our entry has been purged + sStringCache.remove(name); + } + } + return null; + } + + private void putCachedString(ResourceName name, CharSequence cs) { + synchronized (sSync) { + sStringCache.put(name, new WeakReference<CharSequence>(cs)); + } + } + + @Override + public CharSequence getText(String packageName, @StringRes int resid, + ApplicationInfo appInfo) { + ResourceName name = new ResourceName(packageName, resid); + CharSequence text = getCachedString(name); + if (text != null) { + return text; + } + if (appInfo == null) { + try { + appInfo = getApplicationInfo(packageName, sDefaultFlags); + } catch (NameNotFoundException e) { + return null; + } + } + try { + Resources r = getResourcesForApplication(appInfo); + text = r.getText(resid); + putCachedString(name, text); + return text; + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for " + + appInfo.packageName); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving text 0x" + + Integer.toHexString(resid) + " in package " + + packageName, e); + } + return null; + } + + @Override + public XmlResourceParser getXml(String packageName, @XmlRes int resid, + ApplicationInfo appInfo) { + if (appInfo == null) { + try { + appInfo = getApplicationInfo(packageName, sDefaultFlags); + } catch (NameNotFoundException e) { + return null; + } + } + try { + Resources r = getResourcesForApplication(appInfo); + return r.getXml(resid); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving xml 0x" + + Integer.toHexString(resid) + " in package " + + packageName, e); + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for " + + appInfo.packageName); + } + return null; + } + + @Override + public CharSequence getApplicationLabel(ApplicationInfo info) { + return info.loadLabel(this); + } + + @Override + public int installExistingPackage(String packageName) throws NameNotFoundException { + return installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN); + } + + @Override + public int installExistingPackage(String packageName, int installReason) + throws NameNotFoundException { + return installExistingPackageAsUser(packageName, installReason, getUserId()); + } + + @Override + public int installExistingPackageAsUser(String packageName, int userId) + throws NameNotFoundException { + return installExistingPackageAsUser(packageName, PackageManager.INSTALL_REASON_UNKNOWN, + userId); + } + + private int installExistingPackageAsUser(String packageName, int installReason, int userId) + throws NameNotFoundException { + try { + int res = mPM.installExistingPackageAsUser(packageName, userId, + INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, installReason, null); + if (res == INSTALL_FAILED_INVALID_URI) { + throw new NameNotFoundException("Package " + packageName + " doesn't exist"); + } + return res; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void verifyPendingInstall(int id, int response) { + try { + mPM.verifyPendingInstall(id, response); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, + long millisecondsToDelay) { + try { + mPM.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) { + try { + mPM.verifyIntentFilter(id, verificationCode, failedDomains); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getIntentVerificationStatusAsUser(String packageName, int userId) { + try { + return mPM.getIntentVerificationStatus(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) { + try { + return mPM.updateIntentVerificationStatus(packageName, status, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @SuppressWarnings("unchecked") + public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) { + try { + ParceledListSlice<IntentFilterVerificationInfo> parceledList = + mPM.getIntentFilterVerifications(packageName); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @SuppressWarnings("unchecked") + public List<IntentFilter> getAllIntentFilters(String packageName) { + try { + ParceledListSlice<IntentFilter> parceledList = + mPM.getAllIntentFilters(packageName); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String getDefaultBrowserPackageNameAsUser(int userId) { + try { + return mPM.getDefaultBrowserPackageName(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) { + try { + return mPM.setDefaultBrowserPackageName(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + try { + mPM.setInstallerPackageName(targetPackage, installerPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + try { + mPM.setUpdateAvailable(packageName, updateAvailable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String getInstallerPackageName(String packageName) { + try { + return mPM.getInstallerPackageName(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getMoveStatus(int moveId) { + try { + return mPM.getMoveStatus(moveId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void registerMoveCallback(MoveCallback callback, Handler handler) { + synchronized (mDelegates) { + final MoveCallbackDelegate delegate = new MoveCallbackDelegate(callback, + handler.getLooper()); + try { + mPM.registerMoveCallback(delegate); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mDelegates.add(delegate); + } + } + + @Override + public void unregisterMoveCallback(MoveCallback callback) { + synchronized (mDelegates) { + for (Iterator<MoveCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) { + final MoveCallbackDelegate delegate = i.next(); + if (delegate.mCallback == callback) { + try { + mPM.unregisterMoveCallback(delegate); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + i.remove(); + } + } + } + } + + @Override + public int movePackage(String packageName, VolumeInfo vol) { + try { + final String volumeUuid; + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) { + volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL; + } else if (vol.isPrimaryPhysical()) { + volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + volumeUuid = Preconditions.checkNotNull(vol.fsUuid); + } + + return mPM.movePackage(packageName, volumeUuid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @UnsupportedAppUsage + public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + return getPackageCurrentVolume(app, storage); + } + + @VisibleForTesting + protected @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app, + StorageManager storage) { + if (app.isInternal()) { + return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL); + } else { + return storage.findVolumeByUuid(app.volumeUuid); + } + } + + @Override + public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) { + final StorageManager storageManager = mContext.getSystemService(StorageManager.class); + return getPackageCandidateVolumes(app, storageManager, mPM); + } + + @VisibleForTesting + protected @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app, + StorageManager storageManager, IPackageManager pm) { + final VolumeInfo currentVol = getPackageCurrentVolume(app, storageManager); + final List<VolumeInfo> vols = storageManager.getVolumes(); + final List<VolumeInfo> candidates = new ArrayList<>(); + for (VolumeInfo vol : vols) { + if (Objects.equals(vol, currentVol) + || isPackageCandidateVolume(mContext, app, vol, pm)) { + candidates.add(vol); + } + } + return candidates; + } + + @VisibleForTesting + protected boolean isForceAllowOnExternal(Context context) { + return Settings.Global.getInt( + context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + } + + @VisibleForTesting + protected boolean isAllow3rdPartyOnInternal(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_allow3rdPartyAppOnInternal); + } + + private boolean isPackageCandidateVolume( + ContextImpl context, ApplicationInfo app, VolumeInfo vol, IPackageManager pm) { + final boolean forceAllowOnExternal = isForceAllowOnExternal(context); + + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) { + return app.isSystemApp() || isAllow3rdPartyOnInternal(context); + } + + // System apps and apps demanding internal storage can't be moved + // anywhere else + if (app.isSystemApp()) { + return false; + } + if (!forceAllowOnExternal + && (app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY + || app.installLocation == PackageInfo.INSTALL_LOCATION_UNSPECIFIED)) { + return false; + } + + // Gotta be able to write there + if (!vol.isMountedWritable()) { + return false; + } + + // Moving into an ASEC on public primary is only option internal + if (vol.isPrimaryPhysical()) { + return app.isInternal(); + } + + // Some apps can't be moved. (e.g. device admins) + try { + if (pm.isPackageDeviceAdminOnAnyUser(app.packageName)) { + return false; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + // Otherwise we can move to any private volume + return (vol.getType() == VolumeInfo.TYPE_PRIVATE); + } + + @Override + public int movePrimaryStorage(VolumeInfo vol) { + try { + final String volumeUuid; + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) { + volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL; + } else if (vol.isPrimaryPhysical()) { + volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + volumeUuid = Preconditions.checkNotNull(vol.fsUuid); + } + + return mPM.movePrimaryStorage(volumeUuid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public @Nullable VolumeInfo getPrimaryStorageCurrentVolume() { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final String volumeUuid = storage.getPrimaryStorageUuid(); + return storage.findVolumeByQualifiedUuid(volumeUuid); + } + + @Override + public @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes() { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final VolumeInfo currentVol = getPrimaryStorageCurrentVolume(); + final List<VolumeInfo> vols = storage.getVolumes(); + final List<VolumeInfo> candidates = new ArrayList<>(); + if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, + storage.getPrimaryStorageUuid()) && currentVol != null) { + // TODO: support moving primary physical to emulated volume + candidates.add(currentVol); + } else { + for (VolumeInfo vol : vols) { + if (Objects.equals(vol, currentVol) || isPrimaryStorageCandidateVolume(vol)) { + candidates.add(vol); + } + } + } + return candidates; + } + + private static boolean isPrimaryStorageCandidateVolume(VolumeInfo vol) { + // Private internal is always an option + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) { + return true; + } + + // Gotta be able to write there + if (!vol.isMountedWritable()) { + return false; + } + + // We can move to any private volume + return (vol.getType() == VolumeInfo.TYPE_PRIVATE); + } + + @Override + @UnsupportedAppUsage + public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) { + deletePackageAsUser(packageName, observer, flags, getUserId()); + } + + @Override + public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, + int flags, int userId) { + try { + mPM.deletePackageAsUser(packageName, PackageManager.VERSION_CODE_HIGHEST, + observer, userId, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void clearApplicationUserData(String packageName, + IPackageDataObserver observer) { + try { + mPM.clearApplicationUserData(packageName, observer, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override + public void deleteApplicationCacheFiles(String packageName, + IPackageDataObserver observer) { + try { + mPM.deleteApplicationCacheFiles(packageName, observer); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void deleteApplicationCacheFilesAsUser(String packageName, int userId, + IPackageDataObserver observer) { + try { + mPM.deleteApplicationCacheFilesAsUser(packageName, userId, observer); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void freeStorageAndNotify(String volumeUuid, long idealStorageSize, + IPackageDataObserver observer) { + try { + mPM.freeStorageAndNotify(volumeUuid, idealStorageSize, 0, observer); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) { + try { + mPM.freeStorage(volumeUuid, freeStorageSize, 0, pi); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String[] setDistractingPackageRestrictions(String[] packages, int distractionFlags) { + try { + return mPM.setDistractingPackageRestrictionsAsUser(packages, distractionFlags, + mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String[] setPackagesSuspended(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, + String dialogMessage) { + final SuspendDialogInfo dialogInfo = !TextUtils.isEmpty(dialogMessage) + ? new SuspendDialogInfo.Builder().setMessage(dialogMessage).build() + : null; + return setPackagesSuspended(packageNames, suspended, appExtras, launcherExtras, dialogInfo); + } + + @Override + public String[] setPackagesSuspended(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, + SuspendDialogInfo dialogInfo) { + try { + return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras, + launcherExtras, dialogInfo, mContext.getOpPackageName(), + getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public String[] getUnsuspendablePackages(String[] packageNames) { + try { + return mPM.getUnsuspendablePackagesForUser(packageNames, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public Bundle getSuspendedPackageAppExtras() { + final PersistableBundle extras; + try { + extras = mPM.getSuspendedPackageAppExtras(mContext.getOpPackageName(), + getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return extras != null ? new Bundle(extras.deepCopy()) : null; + } + + @Override + public boolean isPackageSuspendedForUser(String packageName, int userId) { + try { + return mPM.isPackageSuspendedForUser(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public boolean isPackageSuspended(String packageName) throws NameNotFoundException { + try { + return isPackageSuspendedForUser(packageName, getUserId()); + } catch (IllegalArgumentException ie) { + throw new NameNotFoundException(packageName); + } + } + + @Override + public boolean isPackageSuspended() { + return isPackageSuspendedForUser(mContext.getOpPackageName(), getUserId()); + } + + /** @hide */ + @Override + public void setApplicationCategoryHint(String packageName, int categoryHint) { + try { + mPM.setApplicationCategoryHint(packageName, categoryHint, + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + @UnsupportedAppUsage + public void getPackageSizeInfoAsUser(String packageName, int userHandle, + IPackageStatsObserver observer) { + final String msg = "Shame on you for calling the hidden API " + + "getPackageSizeInfoAsUser(). Shame!"; + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) { + throw new UnsupportedOperationException(msg); + } else if (observer != null) { + Log.d(TAG, msg); + try { + observer.onGetStatsCompleted(null, false); + } catch (RemoteException ignored) { + } + } + } + + @Override + public void addPackageToPreferred(String packageName) { + Log.w(TAG, "addPackageToPreferred() is a no-op"); + } + + @Override + public void removePackageFromPreferred(String packageName) { + Log.w(TAG, "removePackageFromPreferred() is a no-op"); + } + + @Override + public List<PackageInfo> getPreferredPackages(int flags) { + Log.w(TAG, "getPreferredPackages() is a no-op"); + return Collections.emptyList(); + } + + @Override + public void addPreferredActivity(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity) { + try { + mPM.addPreferredActivity(filter, match, set, activity, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void addPreferredActivityAsUser(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, int userId) { + try { + mPM.addPreferredActivity(filter, match, set, activity, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void replacePreferredActivity(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity) { + try { + mPM.replacePreferredActivity(filter, match, set, activity, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void replacePreferredActivityAsUser(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity, + int userId) { + try { + mPM.replacePreferredActivity(filter, match, set, activity, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void clearPackagePreferredActivities(String packageName) { + try { + mPM.clearPackagePreferredActivities(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getPreferredActivities(List<IntentFilter> outFilters, + List<ComponentName> outActivities, String packageName) { + try { + return mPM.getPreferredActivities(outFilters, outActivities, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public ComponentName getHomeActivities(List<ResolveInfo> outActivities) { + try { + return mPM.getHomeActivities(outActivities); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void setSyntheticAppDetailsActivityEnabled(String packageName, boolean enabled) { + try { + ComponentName componentName = new ComponentName(packageName, + PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME); + mPM.setComponentEnabledSetting(componentName, enabled + ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean getSyntheticAppDetailsActivityEnabled(String packageName) { + try { + ComponentName componentName = new ComponentName(packageName, + PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME); + int state = mPM.getComponentEnabledSetting(componentName, getUserId()); + return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, + int newState, int flags) { + try { + mPM.setComponentEnabledSetting(componentName, newState, flags, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getComponentEnabledSetting(ComponentName componentName) { + try { + return mPM.getComponentEnabledSetting(componentName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void setApplicationEnabledSetting(String packageName, + int newState, int flags) { + try { + mPM.setApplicationEnabledSetting(packageName, newState, flags, + getUserId(), mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int getApplicationEnabledSetting(String packageName) { + try { + return mPM.getApplicationEnabledSetting(packageName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void flushPackageRestrictionsAsUser(int userId) { + try { + mPM.flushPackageRestrictionsAsUser(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, + UserHandle user) { + try { + return mPM.setApplicationHiddenSettingAsUser(packageName, hidden, + user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean getApplicationHiddenSettingAsUser(String packageName, UserHandle user) { + try { + return mPM.getApplicationHiddenSettingAsUser(packageName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public KeySet getKeySetByAlias(String packageName, String alias) { + Preconditions.checkNotNull(packageName); + Preconditions.checkNotNull(alias); + try { + return mPM.getKeySetByAlias(packageName, alias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public KeySet getSigningKeySet(String packageName) { + Preconditions.checkNotNull(packageName); + try { + return mPM.getSigningKeySet(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public boolean isSignedBy(String packageName, KeySet ks) { + Preconditions.checkNotNull(packageName); + Preconditions.checkNotNull(ks); + try { + return mPM.isPackageSignedByKeySet(packageName, ks); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @Override + public boolean isSignedByExactly(String packageName, KeySet ks) { + Preconditions.checkNotNull(packageName); + Preconditions.checkNotNull(ks); + try { + return mPM.isPackageSignedByKeySetExactly(packageName, ks); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @Override + public VerifierDeviceIdentity getVerifierDeviceIdentity() { + try { + return mPM.getVerifierDeviceIdentity(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean isUpgrade() { + return isDeviceUpgrading(); + } + + @Override + public boolean isDeviceUpgrading() { + try { + return mPM.isDeviceUpgrading(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public PackageInstaller getPackageInstaller() { + synchronized (mLock) { + if (mInstaller == null) { + try { + mInstaller = new PackageInstaller(mPM.getPackageInstaller(), + mContext.getPackageName(), getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mInstaller; + } + } + + @Override + public boolean isPackageAvailable(String packageName) { + try { + return mPM.isPackageAvailable(packageName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @Override + public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId, + int flags) { + try { + mPM.addCrossProfileIntentFilter(filter, mContext.getOpPackageName(), + sourceUserId, targetUserId, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @Override + public void clearCrossProfileIntentFilters(int sourceUserId) { + try { + mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) { + Drawable dr = loadUnbadgedItemIcon(itemInfo, appInfo); + if (itemInfo.showUserIcon != UserHandle.USER_NULL) { + return dr; + } + return getUserBadgedIcon(dr, new UserHandle(getUserId())); + } + + /** + * @hide + */ + public Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo, + @Nullable ApplicationInfo appInfo) { + if (itemInfo.showUserIcon != UserHandle.USER_NULL) { + // Indicates itemInfo is for a different user (e.g. a profile's parent), so use a + // generic user icon (users generally lack permission to view each other's actual icons) + int targetUserId = itemInfo.showUserIcon; + return UserIcons.getDefaultUserIcon( + mContext.getResources(), targetUserId, /* light= */ false); + } + Drawable dr = null; + if (itemInfo.packageName != null) { + dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo); + } + if (dr == null && itemInfo != appInfo && appInfo != null) { + dr = loadUnbadgedItemIcon(appInfo, appInfo); + } + if (dr == null) { + dr = itemInfo.loadDefaultIcon(this); + } + return dr; + } + + private Drawable getBadgedDrawable(Drawable drawable, Drawable badgeDrawable, + Rect badgeLocation, boolean tryBadgeInPlace) { + final int badgedWidth = drawable.getIntrinsicWidth(); + final int badgedHeight = drawable.getIntrinsicHeight(); + final boolean canBadgeInPlace = tryBadgeInPlace + && (drawable instanceof BitmapDrawable) + && ((BitmapDrawable) drawable).getBitmap().isMutable(); + + final Bitmap bitmap; + if (canBadgeInPlace) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + bitmap = Bitmap.createBitmap(badgedWidth, badgedHeight, Bitmap.Config.ARGB_8888); + } + Canvas canvas = new Canvas(bitmap); + + if (!canBadgeInPlace) { + drawable.setBounds(0, 0, badgedWidth, badgedHeight); + drawable.draw(canvas); + } + + if (badgeLocation != null) { + if (badgeLocation.left < 0 || badgeLocation.top < 0 + || badgeLocation.width() > badgedWidth || badgeLocation.height() > badgedHeight) { + throw new IllegalArgumentException("Badge location " + badgeLocation + + " not in badged drawable bounds " + + new Rect(0, 0, badgedWidth, badgedHeight)); + } + badgeDrawable.setBounds(0, 0, badgeLocation.width(), badgeLocation.height()); + + canvas.save(); + canvas.translate(badgeLocation.left, badgeLocation.top); + badgeDrawable.draw(canvas); + canvas.restore(); + } else { + badgeDrawable.setBounds(0, 0, badgedWidth, badgedHeight); + badgeDrawable.draw(canvas); + } + + if (!canBadgeInPlace) { + BitmapDrawable mergedDrawable = new BitmapDrawable(mContext.getResources(), bitmap); + + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + mergedDrawable.setTargetDensity(bitmapDrawable.getBitmap().getDensity()); + } + + return mergedDrawable; + } + + return drawable; + } + + private boolean isManagedProfile(int userId) { + return getUserManager().isManagedProfile(userId); + } + + /** + * @hide + */ + @Override + public int getInstallReason(String packageName, UserHandle user) { + try { + return mPM.getInstallReason(packageName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements + Handler.Callback { + private static final int MSG_CREATED = 1; + private static final int MSG_STATUS_CHANGED = 2; + + final MoveCallback mCallback; + final Handler mHandler; + + public MoveCallbackDelegate(MoveCallback callback, Looper looper) { + mCallback = callback; + mHandler = new Handler(looper, this); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_CREATED: { + final SomeArgs args = (SomeArgs) msg.obj; + mCallback.onCreated(args.argi1, (Bundle) args.arg2); + args.recycle(); + return true; + } + case MSG_STATUS_CHANGED: { + final SomeArgs args = (SomeArgs) msg.obj; + mCallback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); + args.recycle(); + return true; + } + } + return false; + } + + @Override + public void onCreated(int moveId, Bundle extras) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.arg2 = extras; + mHandler.obtainMessage(MSG_CREATED, args).sendToTarget(); + } + + @Override + public void onStatusChanged(int moveId, int status, long estMillis) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.argi2 = status; + args.arg3 = estMillis; + mHandler.obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); + } + } + + private final ContextImpl mContext; + @UnsupportedAppUsage + private final IPackageManager mPM; + + /** Assume locked until we hear otherwise */ + private volatile boolean mUserUnlocked = false; + + private static final Object sSync = new Object(); + private static ArrayMap<ResourceName, WeakReference<Drawable.ConstantState>> sIconCache + = new ArrayMap<ResourceName, WeakReference<Drawable.ConstantState>>(); + private static ArrayMap<ResourceName, WeakReference<CharSequence>> sStringCache + = new ArrayMap<ResourceName, WeakReference<CharSequence>>(); + + private final Map<OnPermissionsChangedListener, IOnPermissionsChangeListener> + mPermissionListeners = new ArrayMap<>(); + + public class OnPermissionsChangeListenerDelegate extends IOnPermissionsChangeListener.Stub + implements Handler.Callback{ + private static final int MSG_PERMISSIONS_CHANGED = 1; + + private final OnPermissionsChangedListener mListener; + private final Handler mHandler; + + + public OnPermissionsChangeListenerDelegate(OnPermissionsChangedListener listener, + Looper looper) { + mListener = listener; + mHandler = new Handler(looper, this); + } + + @Override + public void onPermissionsChanged(int uid) { + mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_PERMISSIONS_CHANGED: { + final int uid = msg.arg1; + mListener.onPermissionsChanged(uid); + return true; + } + } + return false; + } + } + + @Override + public boolean canRequestPackageInstalls() { + try { + return mPM.canRequestPackageInstalls(mContext.getPackageName(), getUserId()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public ComponentName getInstantAppResolverSettingsComponent() { + try { + return mPM.getInstantAppResolverSettingsComponent(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public ComponentName getInstantAppInstallerComponent() { + try { + return mPM.getInstantAppInstallerComponent(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public String getInstantAppAndroidId(String packageName, UserHandle user) { + try { + return mPM.getInstantAppAndroidId(packageName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + private static class DexModuleRegisterResult { + final String dexModulePath; + final boolean success; + final String message; + + private DexModuleRegisterResult(String dexModulePath, boolean success, String message) { + this.dexModulePath = dexModulePath; + this.success = success; + this.message = message; + } + } + + private static class DexModuleRegisterCallbackDelegate + extends android.content.pm.IDexModuleRegisterCallback.Stub + implements Handler.Callback { + private static final int MSG_DEX_MODULE_REGISTERED = 1; + private final DexModuleRegisterCallback callback; + private final Handler mHandler; + + DexModuleRegisterCallbackDelegate(@NonNull DexModuleRegisterCallback callback) { + this.callback = callback; + mHandler = new Handler(Looper.getMainLooper(), this); + } + + @Override + public void onDexModuleRegistered(@NonNull String dexModulePath, boolean success, + @Nullable String message)throws RemoteException { + mHandler.obtainMessage(MSG_DEX_MODULE_REGISTERED, + new DexModuleRegisterResult(dexModulePath, success, message)).sendToTarget(); + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.what != MSG_DEX_MODULE_REGISTERED) { + return false; + } + DexModuleRegisterResult result = (DexModuleRegisterResult)msg.obj; + callback.onDexModuleRegistered(result.dexModulePath, result.success, result.message); + return true; + } + } + + @Override + public void registerDexModule(@NonNull String dexModule, + @Nullable DexModuleRegisterCallback callback) { + // Check if this is a shared module by looking if the others can read it. + boolean isSharedModule = false; + try { + StructStat stat = Os.stat(dexModule); + if ((OsConstants.S_IROTH & stat.st_mode) != 0) { + isSharedModule = true; + } + } catch (ErrnoException e) { + callback.onDexModuleRegistered(dexModule, false, + "Could not get stat the module file: " + e.getMessage()); + return; + } + + // Module path is ok. + // Create the callback delegate to be passed to package manager service. + DexModuleRegisterCallbackDelegate callbackDelegate = null; + if (callback != null) { + callbackDelegate = new DexModuleRegisterCallbackDelegate(callback); + } + + // Invoke the package manager service. + try { + mPM.registerDexModule(mContext.getPackageName(), dexModule, + isSharedModule, callbackDelegate); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public CharSequence getHarmfulAppWarning(String packageName) { + try { + return mPM.getHarmfulAppWarning(packageName, getUserId()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public void setHarmfulAppWarning(String packageName, CharSequence warning) { + try { + mPM.setHarmfulAppWarning(packageName, warning, getUserId()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public ArtManager getArtManager() { + synchronized (mLock) { + if (mArtManager == null) { + try { + mArtManager = new ArtManager(mContext, mPM.getArtManager()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mArtManager; + } + } + + @Override + public String getSystemTextClassifierPackageName() { + try { + return mPM.getSystemTextClassifierPackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public String getAttentionServicePackageName() { + try { + return mPM.getAttentionServicePackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public String getWellbeingPackageName() { + try { + return mPM.getWellbeingPackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public String getAppPredictionServicePackageName() { + try { + return mPM.getAppPredictionServicePackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public String getSystemCaptionsServicePackageName() { + try { + return mPM.getSystemCaptionsServicePackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public String getIncidentReportApproverPackageName() { + try { + return mPM.getIncidentReportApproverPackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public boolean isPackageStateProtected(String packageName, int userId) { + try { + return mPM.isPackageStateProtected(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + public void sendDeviceCustomizationReadyBroadcast() { + try { + mPM.sendDeviceCustomizationReadyBroadcast(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } +}
diff --git a/android/app/ApplicationThreadConstants.java b/android/app/ApplicationThreadConstants.java new file mode 100644 index 0000000..1fa670f --- /dev/null +++ b/android/app/ApplicationThreadConstants.java
@@ -0,0 +1,39 @@ +/* + * 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.app; + +/** + * @hide + */ +public final class ApplicationThreadConstants { + public static final int BACKUP_MODE_INCREMENTAL = 0; + public static final int BACKUP_MODE_FULL = 1; + public static final int BACKUP_MODE_RESTORE = 2; + public static final int BACKUP_MODE_RESTORE_FULL = 3; + + public static final int DEBUG_OFF = 0; + public static final int DEBUG_ON = 1; + public static final int DEBUG_WAIT = 2; + + // the package has been removed, clean up internal references + public static final int PACKAGE_REMOVED = 0; + public static final int EXTERNAL_STORAGE_UNAVAILABLE = 1; + // the package is being modified in-place, don't kill it and retain references to it + public static final int PACKAGE_REMOVED_DONT_KILL = 2; + // a previously removed package was replaced with a new version [eg. upgrade, split added, ...] + public static final int PACKAGE_REPLACED = 3; +} \ No newline at end of file
diff --git a/android/app/AuthenticationRequiredException.java b/android/app/AuthenticationRequiredException.java new file mode 100644 index 0000000..0d87336 --- /dev/null +++ b/android/app/AuthenticationRequiredException.java
@@ -0,0 +1,96 @@ +/* + * 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.app; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Specialization of {@link SecurityException} that is thrown when authentication is needed from the + * end user before viewing the content. + * <p> + * This exception is only appropriate where there is a concrete action the user can take to + * authorize and make forward progress, such as confirming or entering authentication credentials, + * or granting access via other means. + * <p class="note"> + * Note: legacy code that receives this exception may treat it as a general + * {@link SecurityException}, and thus there is no guarantee that the action contained will be + * invoked by the user. + * </p> + */ +public final class AuthenticationRequiredException extends SecurityException implements Parcelable { + private static final String TAG = "AuthenticationRequiredException"; + + private final PendingIntent mUserAction; + + /** {@hide} */ + public AuthenticationRequiredException(Parcel in) { + this(new SecurityException(in.readString()), PendingIntent.CREATOR.createFromParcel(in)); + } + + /** + * Create an instance ready to be thrown. + * + * @param cause original cause with details designed for engineering + * audiences. + * @param userAction primary action that will initiate the recovery. This + * must launch an activity that is expected to set + * {@link Activity#setResult(int)} before finishing to + * communicate the final status of the recovery. For example, + * apps that observe {@link Activity#RESULT_OK} may choose to + * immediately retry their operation. + */ + public AuthenticationRequiredException(Throwable cause, PendingIntent userAction) { + super(cause.getMessage()); + mUserAction = Preconditions.checkNotNull(userAction); + } + + /** + * Return primary action that will initiate the authorization. + */ + public PendingIntent getUserAction() { + return mUserAction; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getMessage()); + mUserAction.writeToParcel(dest, flags); + } + + public static final @android.annotation.NonNull Creator<AuthenticationRequiredException> CREATOR = + new Creator<AuthenticationRequiredException>() { + @Override + public AuthenticationRequiredException createFromParcel(Parcel source) { + return new AuthenticationRequiredException(source); + } + + @Override + public AuthenticationRequiredException[] newArray(int size) { + return new AuthenticationRequiredException[size]; + } + }; +}
diff --git a/android/app/AutomaticZenRule.java b/android/app/AutomaticZenRule.java new file mode 100644 index 0000000..7180c01 --- /dev/null +++ b/android/app/AutomaticZenRule.java
@@ -0,0 +1,317 @@ +/** + * 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.NotificationManager.InterruptionFilter; +import android.content.ComponentName; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.service.notification.Condition; +import android.service.notification.ZenPolicy; + +import java.util.Objects; + +/** + * Rule instance information for zen mode. + */ +public final class AutomaticZenRule implements Parcelable { + /* @hide */ + private static final int ENABLED = 1; + /* @hide */ + private static final int DISABLED = 0; + private boolean enabled = false; + private String name; + private @InterruptionFilter int interruptionFilter; + private Uri conditionId; + private ComponentName owner; + private ComponentName configurationActivity; + private long creationTime; + private ZenPolicy mZenPolicy; + private boolean mModified = false; + + /** + * Creates an automatic zen rule. + * + * @param name The name of the rule. + * @param owner The Condition Provider service that owns this rule. + * @param interruptionFilter The interruption filter defines which notifications are allowed to + * interrupt the user (e.g. via sound & vibration) while this rule + * is active. + * @param enabled Whether the rule is enabled. + * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri, + * ZenPolicy, int, boolean)}. + */ + @Deprecated + public AutomaticZenRule(String name, ComponentName owner, Uri conditionId, + int interruptionFilter, boolean enabled) { + this(name, owner, null, conditionId, null, interruptionFilter, enabled); + } + + /** + * Creates an automatic zen rule. + * + * @param name The name of the rule. + * @param owner The Condition Provider service that owns this rule. This can be null if you're + * using {@link NotificationManager#setAutomaticZenRuleState(String, Condition)} + * instead of {@link android.service.notification.ConditionProviderService}. + * @param configurationActivity An activity that handles + * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows + * the user + * more information about this rule and/or allows them to + * configure it. This is required if you are not using a + * {@link android.service.notification.ConditionProviderService}. + * If you are, it overrides the information specified in your + * manifest. + * @param conditionId A representation of the state that should cause your app to apply the + * given interruption filter. + * @param interruptionFilter The interruption filter defines which notifications are allowed to + * interrupt the user (e.g. via sound & vibration) while this rule + * is active. + * @param policy The policy defines which notifications are allowed to interrupt the user + * while this rule is active. This overrides the global policy while this rule is + * action ({@link Condition#STATE_TRUE}). + * @param enabled Whether the rule is enabled. + */ + public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner, + @Nullable ComponentName configurationActivity, @NonNull Uri conditionId, + @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) { + this.name = name; + this.owner = owner; + this.configurationActivity = configurationActivity; + this.conditionId = conditionId; + this.interruptionFilter = interruptionFilter; + this.enabled = enabled; + this.mZenPolicy = policy; + } + + /** + * @hide + */ + public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity, + Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled, + long creationTime) { + this(name, owner, configurationActivity, conditionId, policy, interruptionFilter, enabled); + this.creationTime = creationTime; + } + + public AutomaticZenRule(Parcel source) { + enabled = source.readInt() == ENABLED; + if (source.readInt() == ENABLED) { + name = source.readString(); + } + interruptionFilter = source.readInt(); + conditionId = source.readParcelable(null); + owner = source.readParcelable(null); + configurationActivity = source.readParcelable(null); + creationTime = source.readLong(); + mZenPolicy = source.readParcelable(null); + mModified = source.readInt() == ENABLED; + } + + /** + * Returns the {@link ComponentName} of the condition provider service that owns this rule. + */ + public ComponentName getOwner() { + return owner; + } + + /** + * Returns the {@link ComponentName} of the activity that shows configuration options + * for this rule. + */ + public @Nullable ComponentName getConfigurationActivity() { + return configurationActivity; + } + + /** + * Returns the representation of the state that causes this rule to become active. + */ + public Uri getConditionId() { + return conditionId; + } + + /** + * Returns the interruption filter that is applied when this rule is active. + */ + public int getInterruptionFilter() { + return interruptionFilter; + } + + /** + * Returns the name of this rule. + */ + public String getName() { + return name; + } + + /** + * Returns whether this rule is enabled. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Returns whether this rule's name has been modified by the user. + * @hide + */ + public boolean isModified() { + return mModified; + } + + /** + * Gets the zen policy. + */ + public ZenPolicy getZenPolicy() { + return mZenPolicy == null ? null : this.mZenPolicy.copy(); + } + + /** + * Returns the time this rule was created, represented as milliseconds since the epoch. + */ + public long getCreationTime() { + return creationTime; + } + + /** + * Sets the representation of the state that causes this rule to become active. + */ + public void setConditionId(Uri conditionId) { + this.conditionId = conditionId; + } + + /** + * Sets the interruption filter that is applied when this rule is active. + * @param interruptionFilter The do not disturb mode to enter when this rule is active. + */ + public void setInterruptionFilter(@InterruptionFilter int interruptionFilter) { + this.interruptionFilter = interruptionFilter; + } + + /** + * Sets the name of this rule. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Enables this rule. + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Sets modified state of this rule. + * @hide + */ + public void setModified(boolean modified) { + this.mModified = modified; + } + + /** + * Sets the zen policy. + */ + public void setZenPolicy(ZenPolicy zenPolicy) { + this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy()); + } + + /** + * Sets the configuration activity - an activity that handles + * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information + * about this rule and/or allows them to configure it. This is required to be non-null for rules + * that are not backed by {@link android.service.notification.ConditionProviderService}. + */ + public void setConfigurationActivity(@Nullable ComponentName componentName) { + this.configurationActivity = componentName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(enabled ? ENABLED : DISABLED); + if (name != null) { + dest.writeInt(1); + dest.writeString(name); + } else { + dest.writeInt(0); + } + dest.writeInt(interruptionFilter); + dest.writeParcelable(conditionId, 0); + dest.writeParcelable(owner, 0); + dest.writeParcelable(configurationActivity, 0); + dest.writeLong(creationTime); + dest.writeParcelable(mZenPolicy, 0); + dest.writeInt(mModified ? ENABLED : DISABLED); + } + + @Override + public String toString() { + return new StringBuilder(AutomaticZenRule.class.getSimpleName()).append('[') + .append("enabled=").append(enabled) + .append(",name=").append(name) + .append(",interruptionFilter=").append(interruptionFilter) + .append(",conditionId=").append(conditionId) + .append(",owner=").append(owner) + .append(",configActivity=").append(configurationActivity) + .append(",creationTime=").append(creationTime) + .append(",mZenPolicy=").append(mZenPolicy) + .append(']').toString(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AutomaticZenRule)) return false; + if (o == this) return true; + final AutomaticZenRule other = (AutomaticZenRule) o; + return other.enabled == enabled + && other.mModified == mModified + && Objects.equals(other.name, name) + && other.interruptionFilter == interruptionFilter + && Objects.equals(other.conditionId, conditionId) + && Objects.equals(other.owner, owner) + && Objects.equals(other.mZenPolicy, mZenPolicy) + && Objects.equals(other.configurationActivity, configurationActivity) + && other.creationTime == creationTime; + } + + @Override + public int hashCode() { + return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, + configurationActivity, mZenPolicy, mModified, creationTime); + } + + public static final @android.annotation.NonNull Parcelable.Creator<AutomaticZenRule> CREATOR + = new Parcelable.Creator<AutomaticZenRule>() { + @Override + public AutomaticZenRule createFromParcel(Parcel source) { + return new AutomaticZenRule(source); + } + @Override + public AutomaticZenRule[] newArray(int size) { + return new AutomaticZenRule[size]; + } + }; +}
diff --git a/android/app/BackStackRecord.java b/android/app/BackStackRecord.java new file mode 100644 index 0000000..351e737 --- /dev/null +++ b/android/app/BackStackRecord.java
@@ -0,0 +1,1024 @@ +/* + * 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.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.LogWriter; +import android.view.View; + +import com.android.internal.util.FastPrintWriter; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.reflect.Modifier; +import java.util.ArrayList; + +final class BackStackState implements Parcelable { + final int[] mOps; + final int mTransition; + final int mTransitionStyle; + final String mName; + final int mIndex; + final int mBreadCrumbTitleRes; + final CharSequence mBreadCrumbTitleText; + final int mBreadCrumbShortTitleRes; + final CharSequence mBreadCrumbShortTitleText; + final ArrayList<String> mSharedElementSourceNames; + final ArrayList<String> mSharedElementTargetNames; + final boolean mReorderingAllowed; + + public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { + final int numOps = bse.mOps.size(); + mOps = new int[numOps * 6]; + + if (!bse.mAddToBackStack) { + throw new IllegalStateException("Not on back stack"); + } + + int pos = 0; + for (int opNum = 0; opNum < numOps; opNum++) { + final BackStackRecord.Op op = bse.mOps.get(opNum); + mOps[pos++] = op.cmd; + mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1; + mOps[pos++] = op.enterAnim; + mOps[pos++] = op.exitAnim; + mOps[pos++] = op.popEnterAnim; + mOps[pos++] = op.popExitAnim; + } + mTransition = bse.mTransition; + mTransitionStyle = bse.mTransitionStyle; + mName = bse.mName; + mIndex = bse.mIndex; + mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes; + mBreadCrumbTitleText = bse.mBreadCrumbTitleText; + mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; + mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; + mSharedElementSourceNames = bse.mSharedElementSourceNames; + mSharedElementTargetNames = bse.mSharedElementTargetNames; + mReorderingAllowed = bse.mReorderingAllowed; + } + + public BackStackState(Parcel in) { + mOps = in.createIntArray(); + mTransition = in.readInt(); + mTransitionStyle = in.readInt(); + mName = in.readString(); + mIndex = in.readInt(); + mBreadCrumbTitleRes = in.readInt(); + mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mBreadCrumbShortTitleRes = in.readInt(); + mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSharedElementSourceNames = in.createStringArrayList(); + mSharedElementTargetNames = in.createStringArrayList(); + mReorderingAllowed = in.readInt() != 0; + } + + public BackStackRecord instantiate(FragmentManagerImpl fm) { + BackStackRecord bse = new BackStackRecord(fm); + int pos = 0; + int num = 0; + while (pos < mOps.length) { + BackStackRecord.Op op = new BackStackRecord.Op(); + op.cmd = mOps[pos++]; + if (FragmentManagerImpl.DEBUG) { + Log.v(FragmentManagerImpl.TAG, + "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); + } + int findex = mOps[pos++]; + if (findex >= 0) { + Fragment f = fm.mActive.get(findex); + op.fragment = f; + } else { + op.fragment = null; + } + op.enterAnim = mOps[pos++]; + op.exitAnim = mOps[pos++]; + op.popEnterAnim = mOps[pos++]; + op.popExitAnim = mOps[pos++]; + bse.mEnterAnim = op.enterAnim; + bse.mExitAnim = op.exitAnim; + bse.mPopEnterAnim = op.popEnterAnim; + bse.mPopExitAnim = op.popExitAnim; + bse.addOp(op); + num++; + } + bse.mTransition = mTransition; + bse.mTransitionStyle = mTransitionStyle; + bse.mName = mName; + bse.mIndex = mIndex; + bse.mAddToBackStack = true; + bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes; + bse.mBreadCrumbTitleText = mBreadCrumbTitleText; + bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; + bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; + bse.mSharedElementSourceNames = mSharedElementSourceNames; + bse.mSharedElementTargetNames = mSharedElementTargetNames; + bse.mReorderingAllowed = mReorderingAllowed; + bse.bumpBackStackNesting(1); + return bse; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeIntArray(mOps); + dest.writeInt(mTransition); + dest.writeInt(mTransitionStyle); + dest.writeString(mName); + dest.writeInt(mIndex); + dest.writeInt(mBreadCrumbTitleRes); + TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); + dest.writeInt(mBreadCrumbShortTitleRes); + TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); + dest.writeStringList(mSharedElementSourceNames); + dest.writeStringList(mSharedElementTargetNames); + dest.writeInt(mReorderingAllowed ? 1 : 0); + } + + public static final @android.annotation.NonNull Parcelable.Creator<BackStackState> CREATOR + = new Parcelable.Creator<BackStackState>() { + public BackStackState createFromParcel(Parcel in) { + return new BackStackState(in); + } + + public BackStackState[] newArray(int size) { + return new BackStackState[size]; + } + }; +} + +/** + * @hide Entry of an operation on the fragment back stack. + */ +final class BackStackRecord extends FragmentTransaction implements + FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator { + static final String TAG = FragmentManagerImpl.TAG; + + final FragmentManagerImpl mManager; + + static final int OP_NULL = 0; + static final int OP_ADD = 1; + static final int OP_REPLACE = 2; + static final int OP_REMOVE = 3; + static final int OP_HIDE = 4; + static final int OP_SHOW = 5; + static final int OP_DETACH = 6; + static final int OP_ATTACH = 7; + static final int OP_SET_PRIMARY_NAV = 8; + static final int OP_UNSET_PRIMARY_NAV = 9; + + static final class Op { + int cmd; + Fragment fragment; + int enterAnim; + int exitAnim; + int popEnterAnim; + int popExitAnim; + + Op() { + } + + Op(int cmd, Fragment fragment) { + this.cmd = cmd; + this.fragment = fragment; + } + } + + ArrayList<Op> mOps = new ArrayList<>(); + int mEnterAnim; + int mExitAnim; + int mPopEnterAnim; + int mPopExitAnim; + int mTransition; + int mTransitionStyle; + boolean mAddToBackStack; + boolean mAllowAddToBackStack = true; + String mName; + boolean mCommitted; + int mIndex = -1; + boolean mReorderingAllowed; + + ArrayList<Runnable> mCommitRunnables; + + int mBreadCrumbTitleRes; + CharSequence mBreadCrumbTitleText; + int mBreadCrumbShortTitleRes; + CharSequence mBreadCrumbShortTitleText; + + ArrayList<String> mSharedElementSourceNames; + ArrayList<String> mSharedElementTargetNames; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("BackStackEntry{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + if (mIndex >= 0) { + sb.append(" #"); + sb.append(mIndex); + } + if (mName != null) { + sb.append(" "); + sb.append(mName); + } + sb.append("}"); + return sb.toString(); + } + + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + dump(prefix, writer, true); + } + + void dump(String prefix, PrintWriter writer, boolean full) { + if (full) { + writer.print(prefix); + writer.print("mName="); + writer.print(mName); + writer.print(" mIndex="); + writer.print(mIndex); + writer.print(" mCommitted="); + writer.println(mCommitted); + if (mTransition != FragmentTransaction.TRANSIT_NONE) { + writer.print(prefix); + writer.print("mTransition=#"); + writer.print(Integer.toHexString(mTransition)); + writer.print(" mTransitionStyle=#"); + writer.println(Integer.toHexString(mTransitionStyle)); + } + if (mEnterAnim != 0 || mExitAnim != 0) { + writer.print(prefix); + writer.print("mEnterAnim=#"); + writer.print(Integer.toHexString(mEnterAnim)); + writer.print(" mExitAnim=#"); + writer.println(Integer.toHexString(mExitAnim)); + } + if (mPopEnterAnim != 0 || mPopExitAnim != 0) { + writer.print(prefix); + writer.print("mPopEnterAnim=#"); + writer.print(Integer.toHexString(mPopEnterAnim)); + writer.print(" mPopExitAnim=#"); + writer.println(Integer.toHexString(mPopExitAnim)); + } + if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { + writer.print(prefix); + writer.print("mBreadCrumbTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbTitleRes)); + writer.print(" mBreadCrumbTitleText="); + writer.println(mBreadCrumbTitleText); + } + if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { + writer.print(prefix); + writer.print("mBreadCrumbShortTitleRes=#"); + writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); + writer.print(" mBreadCrumbShortTitleText="); + writer.println(mBreadCrumbShortTitleText); + } + } + + if (!mOps.isEmpty()) { + writer.print(prefix); + writer.println("Operations:"); + String innerPrefix = prefix + " "; + final int numOps = mOps.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + final Op op = mOps.get(opNum); + String cmdStr; + switch (op.cmd) { + case OP_NULL: + cmdStr = "NULL"; + break; + case OP_ADD: + cmdStr = "ADD"; + break; + case OP_REPLACE: + cmdStr = "REPLACE"; + break; + case OP_REMOVE: + cmdStr = "REMOVE"; + break; + case OP_HIDE: + cmdStr = "HIDE"; + break; + case OP_SHOW: + cmdStr = "SHOW"; + break; + case OP_DETACH: + cmdStr = "DETACH"; + break; + case OP_ATTACH: + cmdStr = "ATTACH"; + break; + case OP_SET_PRIMARY_NAV: + cmdStr="SET_PRIMARY_NAV"; + break; + case OP_UNSET_PRIMARY_NAV: + cmdStr="UNSET_PRIMARY_NAV"; + break; + + default: + cmdStr = "cmd=" + op.cmd; + break; + } + writer.print(prefix); + writer.print(" Op #"); + writer.print(opNum); + writer.print(": "); + writer.print(cmdStr); + writer.print(" "); + writer.println(op.fragment); + if (full) { + if (op.enterAnim != 0 || op.exitAnim != 0) { + writer.print(innerPrefix); + writer.print("enterAnim=#"); + writer.print(Integer.toHexString(op.enterAnim)); + writer.print(" exitAnim=#"); + writer.println(Integer.toHexString(op.exitAnim)); + } + if (op.popEnterAnim != 0 || op.popExitAnim != 0) { + writer.print(innerPrefix); + writer.print("popEnterAnim=#"); + writer.print(Integer.toHexString(op.popEnterAnim)); + writer.print(" popExitAnim=#"); + writer.println(Integer.toHexString(op.popExitAnim)); + } + } + } + } + } + + public BackStackRecord(FragmentManagerImpl manager) { + mManager = manager; + mReorderingAllowed = mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1; + } + + public int getId() { + return mIndex; + } + + public int getBreadCrumbTitleRes() { + return mBreadCrumbTitleRes; + } + + public int getBreadCrumbShortTitleRes() { + return mBreadCrumbShortTitleRes; + } + + public CharSequence getBreadCrumbTitle() { + if (mBreadCrumbTitleRes != 0 && mManager.mHost != null) { + return mManager.mHost.getContext().getText(mBreadCrumbTitleRes); + } + return mBreadCrumbTitleText; + } + + public CharSequence getBreadCrumbShortTitle() { + if (mBreadCrumbShortTitleRes != 0 && mManager.mHost != null) { + return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes); + } + return mBreadCrumbShortTitleText; + } + + void addOp(Op op) { + mOps.add(op); + op.enterAnim = mEnterAnim; + op.exitAnim = mExitAnim; + op.popEnterAnim = mPopEnterAnim; + op.popExitAnim = mPopExitAnim; + } + + public FragmentTransaction add(Fragment fragment, String tag) { + doAddOp(0, fragment, tag, OP_ADD); + return this; + } + + public FragmentTransaction add(int containerViewId, Fragment fragment) { + doAddOp(containerViewId, fragment, null, OP_ADD); + return this; + } + + public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { + doAddOp(containerViewId, fragment, tag, OP_ADD); + return this; + } + + private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { + if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) { + final Class fragmentClass = fragment.getClass(); + final int modifiers = fragmentClass.getModifiers(); + if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers) + || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers)))) { + throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName() + + " must be a public static class to be properly recreated from" + + " instance state."); + } + } + fragment.mFragmentManager = mManager; + + if (tag != null) { + if (fragment.mTag != null && !tag.equals(fragment.mTag)) { + throw new IllegalStateException("Can't change tag of fragment " + + fragment + ": was " + fragment.mTag + + " now " + tag); + } + fragment.mTag = tag; + } + + if (containerViewId != 0) { + if (containerViewId == View.NO_ID) { + throw new IllegalArgumentException("Can't add fragment " + + fragment + " with tag " + tag + " to container view with no id"); + } + if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { + throw new IllegalStateException("Can't change container ID of fragment " + + fragment + ": was " + fragment.mFragmentId + + " now " + containerViewId); + } + fragment.mContainerId = fragment.mFragmentId = containerViewId; + } + + addOp(new Op(opcmd, fragment)); + } + + public FragmentTransaction replace(int containerViewId, Fragment fragment) { + return replace(containerViewId, fragment, null); + } + + public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { + if (containerViewId == 0) { + throw new IllegalArgumentException("Must use non-zero containerViewId"); + } + + doAddOp(containerViewId, fragment, tag, OP_REPLACE); + return this; + } + + public FragmentTransaction remove(Fragment fragment) { + addOp(new Op(OP_REMOVE, fragment)); + + return this; + } + + public FragmentTransaction hide(Fragment fragment) { + addOp(new Op(OP_HIDE, fragment)); + + return this; + } + + public FragmentTransaction show(Fragment fragment) { + addOp(new Op(OP_SHOW, fragment)); + + return this; + } + + public FragmentTransaction detach(Fragment fragment) { + addOp(new Op(OP_DETACH, fragment)); + + return this; + } + + public FragmentTransaction attach(Fragment fragment) { + addOp(new Op(OP_ATTACH, fragment)); + + return this; + } + + public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) { + addOp(new Op(OP_SET_PRIMARY_NAV, fragment)); + + return this; + } + + public FragmentTransaction setCustomAnimations(int enter, int exit) { + return setCustomAnimations(enter, exit, 0, 0); + } + + public FragmentTransaction setCustomAnimations(int enter, int exit, + int popEnter, int popExit) { + mEnterAnim = enter; + mExitAnim = exit; + mPopEnterAnim = popEnter; + mPopExitAnim = popExit; + return this; + } + + public FragmentTransaction setTransition(int transition) { + mTransition = transition; + return this; + } + + @Override + public FragmentTransaction addSharedElement(View sharedElement, String name) { + String transitionName = sharedElement.getTransitionName(); + if (transitionName == null) { + throw new IllegalArgumentException("Unique transitionNames are required for all" + + " sharedElements"); + } + if (mSharedElementSourceNames == null) { + mSharedElementSourceNames = new ArrayList<String>(); + mSharedElementTargetNames = new ArrayList<String>(); + } else if (mSharedElementTargetNames.contains(name)) { + throw new IllegalArgumentException("A shared element with the target name '" + + name + "' has already been added to the transaction."); + } else if (mSharedElementSourceNames.contains(transitionName)) { + throw new IllegalArgumentException("A shared element with the source name '" + + transitionName + " has already been added to the transaction."); + } + mSharedElementSourceNames.add(transitionName); + mSharedElementTargetNames.add(name); + return this; + } + + public FragmentTransaction setTransitionStyle(int styleRes) { + mTransitionStyle = styleRes; + return this; + } + + public FragmentTransaction addToBackStack(String name) { + if (!mAllowAddToBackStack) { + throw new IllegalStateException( + "This FragmentTransaction is not allowed to be added to the back stack."); + } + mAddToBackStack = true; + mName = name; + return this; + } + + public boolean isAddToBackStackAllowed() { + return mAllowAddToBackStack; + } + + public FragmentTransaction disallowAddToBackStack() { + if (mAddToBackStack) { + throw new IllegalStateException( + "This transaction is already being added to the back stack"); + } + mAllowAddToBackStack = false; + return this; + } + + public FragmentTransaction setBreadCrumbTitle(int res) { + mBreadCrumbTitleRes = res; + mBreadCrumbTitleText = null; + return this; + } + + public FragmentTransaction setBreadCrumbTitle(CharSequence text) { + mBreadCrumbTitleRes = 0; + mBreadCrumbTitleText = text; + return this; + } + + public FragmentTransaction setBreadCrumbShortTitle(int res) { + mBreadCrumbShortTitleRes = res; + mBreadCrumbShortTitleText = null; + return this; + } + + public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) { + mBreadCrumbShortTitleRes = 0; + mBreadCrumbShortTitleText = text; + return this; + } + + void bumpBackStackNesting(int amt) { + if (!mAddToBackStack) { + return; + } + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting in " + this + + " by " + amt); + } + final int numOps = mOps.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + final Op op = mOps.get(opNum); + if (op.fragment != null) { + op.fragment.mBackStackNesting += amt; + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Bump nesting of " + + op.fragment + " to " + op.fragment.mBackStackNesting); + } + } + } + } + + @Override + public FragmentTransaction runOnCommit(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("runnable cannot be null"); + } + disallowAddToBackStack(); + if (mCommitRunnables == null) { + mCommitRunnables = new ArrayList<>(); + } + mCommitRunnables.add(runnable); + return this; + } + + public void runOnCommitRunnables() { + if (mCommitRunnables != null) { + for (int i = 0, N = mCommitRunnables.size(); i < N; i++) { + mCommitRunnables.get(i).run(); + } + mCommitRunnables = null; + } + } + + public int commit() { + return commitInternal(false); + } + + public int commitAllowingStateLoss() { + return commitInternal(true); + } + + @Override + public void commitNow() { + disallowAddToBackStack(); + mManager.execSingleAction(this, false); + } + + @Override + public void commitNowAllowingStateLoss() { + disallowAddToBackStack(); + mManager.execSingleAction(this, true); + } + + @Override + public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) { + mReorderingAllowed = reorderingAllowed; + return this; + } + + int commitInternal(boolean allowStateLoss) { + if (mCommitted) { + throw new IllegalStateException("commit already called"); + } + if (FragmentManagerImpl.DEBUG) { + Log.v(TAG, "Commit: " + this); + LogWriter logw = new LogWriter(Log.VERBOSE, TAG); + PrintWriter pw = new FastPrintWriter(logw, false, 1024); + dump(" ", null, pw, null); + pw.flush(); + } + mCommitted = true; + if (mAddToBackStack) { + mIndex = mManager.allocBackStackIndex(this); + } else {