Merge "Update android.app.ui_rich_ongoing to use the new FR" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index a131ea7..a071668 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -33929,9 +33929,12 @@
public class RemoteCallbackList<E extends android.os.IInterface> {
ctor public RemoteCallbackList();
method public int beginBroadcast();
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") public void broadcast(@NonNull java.util.function.Consumer<E>);
method public void finishBroadcast();
method public Object getBroadcastCookie(int);
method public E getBroadcastItem(int);
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getFrozenCalleePolicy();
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getMaxQueueSize();
method public Object getRegisteredCallbackCookie(int);
method public int getRegisteredCallbackCount();
method public E getRegisteredCallbackItem(int);
@@ -33941,6 +33944,16 @@
method public boolean register(E);
method public boolean register(E, Object);
method public boolean unregister(E);
+ field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_DROP = 3; // 0x3
+ field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_ENQUEUE_ALL = 1; // 0x1
+ field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT = 2; // 0x2
+ field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_UNSET = 0; // 0x0
+ }
+
+ @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final class RemoteCallbackList.Builder<E extends android.os.IInterface> {
+ ctor public RemoteCallbackList.Builder(int);
+ method @NonNull public android.os.RemoteCallbackList<E> build();
+ method @NonNull public android.os.RemoteCallbackList.Builder setMaxQueueSize(int);
}
public class RemoteException extends android.util.AndroidException {
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 769cbdd..f82c822 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -16,11 +16,18 @@
package android.os;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Slog;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -30,7 +37,7 @@
* {@link android.app.Service} to its clients. In particular, this:
*
* <ul>
- * <li> Keeps track of a set of registered {@link IInterface} callbacks,
+ * <li> Keeps track of a set of registered {@link IInterface} objects,
* taking care to identify them through their underlying unique {@link IBinder}
* (by calling {@link IInterface#asBinder IInterface.asBinder()}.
* <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to
@@ -47,7 +54,7 @@
* the registered clients, use {@link #beginBroadcast},
* {@link #getBroadcastItem}, and {@link #finishBroadcast}.
*
- * <p>If a registered callback's process goes away, this class will take
+ * <p>If a registered interface's process goes away, this class will take
* care of automatically removing it from the list. If you want to do
* additional work in this situation, you can create a subclass that
* implements the {@link #onCallbackDied} method.
@@ -56,78 +63,310 @@
public class RemoteCallbackList<E extends IInterface> {
private static final String TAG = "RemoteCallbackList";
+ private static final int DEFAULT_MAX_QUEUE_SIZE = 1000;
+
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"FROZEN_CALLEE_POLICY_"}, value = {
+ FROZEN_CALLEE_POLICY_UNSET,
+ FROZEN_CALLEE_POLICY_ENQUEUE_ALL,
+ FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT,
+ FROZEN_CALLEE_POLICY_DROP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface FrozenCalleePolicy {
+ }
+
+ /**
+ * Callbacks are invoked immediately regardless of the frozen state of the target process.
+ *
+ * Not recommended. Only exists for backward-compatibility. This represents the behavior up to
+ * SDK 35. Starting with SDK 36, clients should set a policy to govern callback invocations when
+ * recipients are frozen.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public static final int FROZEN_CALLEE_POLICY_UNSET = 0;
+
+ /**
+ * When the callback recipient's process is frozen, callbacks are enqueued so they're invoked
+ * after the recipient is unfrozen.
+ *
+ * This is commonly used when the recipient wants to receive all callbacks without losing any
+ * history, e.g. the recipient maintains a running count of events that occurred.
+ *
+ * Queued callbacks are invoked in the order they were originally broadcasted.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public static final int FROZEN_CALLEE_POLICY_ENQUEUE_ALL = 1;
+
+ /**
+ * When the callback recipient's process is frozen, only the most recent callback is enqueued,
+ * which is later invoked after the recipient is unfrozen.
+ *
+ * This can be used when only the most recent state matters, for instance when clients are
+ * listening to screen brightness changes.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public static final int FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT = 2;
+
+ /**
+ * When the callback recipient's process is frozen, callbacks are suppressed as if they never
+ * happened.
+ *
+ * This could be useful in the case where the recipient wishes to react to callbacks only when
+ * they occur while the recipient is not frozen. For example, certain network events are only
+ * worth responding to if the response can be immediate. Another example is recipients having
+ * another way of getting the latest state once it's unfrozen. Therefore there is no need to
+ * save callbacks that happened while the recipient was frozen.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public static final int FROZEN_CALLEE_POLICY_DROP = 3;
+
@UnsupportedAppUsage
- /*package*/ ArrayMap<IBinder, Callback> mCallbacks
- = new ArrayMap<IBinder, Callback>();
+ /*package*/ ArrayMap<IBinder, Interface> mInterfaces = new ArrayMap<IBinder, Interface>();
private Object[] mActiveBroadcast;
private int mBroadcastCount = -1;
private boolean mKilled = false;
private StringBuilder mRecentCallers;
- private final class Callback implements IBinder.DeathRecipient {
- final E mCallback;
- final Object mCookie;
+ private final @FrozenCalleePolicy int mFrozenCalleePolicy;
+ private final int mMaxQueueSize;
- Callback(E callback, Object cookie) {
- mCallback = callback;
+ private final class Interface implements IBinder.DeathRecipient,
+ IBinder.FrozenStateChangeCallback {
+ final IBinder mBinder;
+ final E mInterface;
+ final Object mCookie;
+ final Queue<Consumer<E>> mCallbackQueue;
+ int mCurrentState = IBinder.FrozenStateChangeCallback.STATE_UNFROZEN;
+
+ Interface(E callbackInterface, Object cookie) {
+ mBinder = callbackInterface.asBinder();
+ mInterface = callbackInterface;
mCookie = cookie;
+ mCallbackQueue = mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_ENQUEUE_ALL
+ || mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT
+ ? new ConcurrentLinkedQueue<>() : null;
+ }
+
+ @Override
+ public synchronized void onFrozenStateChanged(@NonNull IBinder who, int state) {
+ if (state == STATE_UNFROZEN && mCallbackQueue != null) {
+ while (!mCallbackQueue.isEmpty()) {
+ Consumer<E> callback = mCallbackQueue.poll();
+ callback.accept(mInterface);
+ }
+ }
+ mCurrentState = state;
+ }
+
+ void addCallback(@NonNull Consumer<E> callback) {
+ if (mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_UNSET) {
+ callback.accept(mInterface);
+ return;
+ }
+ synchronized (this) {
+ if (mCurrentState == STATE_UNFROZEN) {
+ callback.accept(mInterface);
+ return;
+ }
+ switch (mFrozenCalleePolicy) {
+ case FROZEN_CALLEE_POLICY_ENQUEUE_ALL:
+ if (mCallbackQueue.size() >= mMaxQueueSize) {
+ mCallbackQueue.poll();
+ }
+ mCallbackQueue.offer(callback);
+ break;
+ case FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT:
+ mCallbackQueue.clear();
+ mCallbackQueue.offer(callback);
+ break;
+ case FROZEN_CALLEE_POLICY_DROP:
+ // Do nothing. Just ignore the callback.
+ break;
+ case FROZEN_CALLEE_POLICY_UNSET:
+ // Do nothing. Should have returned at the start of the method.
+ break;
+ }
+ }
+ }
+
+ public void maybeSubscribeToFrozenCallback() throws RemoteException {
+ if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) {
+ mBinder.addFrozenStateChangeCallback(this);
+ }
+ }
+
+ public void maybeUnsubscribeFromFrozenCallback() {
+ if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) {
+ mBinder.removeFrozenStateChangeCallback(this);
+ }
}
public void binderDied() {
- synchronized (mCallbacks) {
- mCallbacks.remove(mCallback.asBinder());
+ synchronized (mInterfaces) {
+ mInterfaces.remove(mBinder);
+ maybeUnsubscribeFromFrozenCallback();
}
- onCallbackDied(mCallback, mCookie);
+ onCallbackDied(mInterface, mCookie);
}
}
/**
+ * Builder for {@link RemoteCallbackList}.
+ *
+ * @param <E> The remote callback interface type.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public static final class Builder<E extends IInterface> {
+ private @FrozenCalleePolicy int mFrozenCalleePolicy;
+ private int mMaxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
+
+ /**
+ * Creates a Builder for {@link RemoteCallbackList}.
+ *
+ * @param frozenCalleePolicy When the callback recipient's process is frozen, this parameter
+ * specifies when/whether callbacks are invoked. It's important to choose a strategy that's
+ * right for the use case. Leaving the policy unset with {@link #FROZEN_CALLEE_POLICY_UNSET}
+ * is not recommended as it allows callbacks to be invoked while the recipient is frozen.
+ */
+ public Builder(@FrozenCalleePolicy int frozenCalleePolicy) {
+ mFrozenCalleePolicy = frozenCalleePolicy;
+ }
+
+ /**
+ * Sets the max queue size.
+ *
+ * @param maxQueueSize The max size limit on the queue that stores callbacks added when the
+ * recipient's process is frozen. Once the limit is reached, the oldest callback is dropped
+ * to keep the size under the limit. Should only be called for
+ * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}.
+ *
+ * @return This builder.
+ * @throws IllegalArgumentException if the maxQueueSize is not positive.
+ * @throws UnsupportedOperationException if frozenCalleePolicy is not
+ * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}.
+ */
+ public @NonNull Builder setMaxQueueSize(int maxQueueSize) {
+ if (maxQueueSize <= 0) {
+ throw new IllegalArgumentException("maxQueueSize must be positive");
+ }
+ if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_ENQUEUE_ALL) {
+ throw new UnsupportedOperationException(
+ "setMaxQueueSize can only be called for FROZEN_CALLEE_POLICY_ENQUEUE_ALL");
+ }
+ mMaxQueueSize = maxQueueSize;
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link RemoteCallbackList}.
+ *
+ * @return The built {@link RemoteCallbackList} object.
+ */
+ public @NonNull RemoteCallbackList<E> build() {
+ return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize);
+ }
+ }
+
+ /**
+ * Returns the frozen callee policy.
+ *
+ * @return The frozen callee policy.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public @FrozenCalleePolicy int getFrozenCalleePolicy() {
+ return mFrozenCalleePolicy;
+ }
+
+ /**
+ * Returns the max queue size.
+ *
+ * @return The max queue size.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public int getMaxQueueSize() {
+ return mMaxQueueSize;
+ }
+
+ /**
+ * Creates a RemoteCallbackList with {@link #FROZEN_CALLEE_POLICY_UNSET}. This is equivalent to
+ * <pre>
+ * new RemoteCallbackList.Build(RemoteCallbackList.FROZEN_CALLEE_POLICY_UNSET).build()
+ * </pre>
+ */
+ public RemoteCallbackList() {
+ this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE);
+ }
+
+ /**
+ * Creates a RemoteCallbackList with the specified frozen callee policy.
+ *
+ * @param frozenCalleePolicy When the callback recipient's process is frozen, this parameter
+ * specifies when/whether callbacks are invoked. It's important to choose a strategy that's
+ * right for the use case. Leaving the policy unset with {@link #FROZEN_CALLEE_POLICY_UNSET}
+ * is not recommended as it allows callbacks to be invoked while the recipient is frozen.
+ *
+ * @param maxQueueSize The max size limit on the queue that stores callbacks added when the
+ * recipient's process is frozen. Once the limit is reached, the oldest callbacks would be
+ * dropped to keep the size under limit. Ignored except for
+ * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}.
+ */
+ private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize) {
+ mFrozenCalleePolicy = frozenCalleePolicy;
+ mMaxQueueSize = maxQueueSize;
+ }
+
+ /**
* Simple version of {@link RemoteCallbackList#register(E, Object)}
* that does not take a cookie object.
*/
- public boolean register(E callback) {
- return register(callback, null);
+ public boolean register(E callbackInterface) {
+ return register(callbackInterface, null);
}
/**
- * Add a new callback to the list. This callback will remain in the list
+ * Add a new interface to the list. This interface will remain in the list
* until a corresponding call to {@link #unregister} or its hosting process
- * goes away. If the callback was already registered (determined by
- * checking to see if the {@link IInterface#asBinder callback.asBinder()}
- * object is already in the list), then it will be replaced with the new callback.
+ * goes away. If the interface was already registered (determined by
+ * checking to see if the {@link IInterface#asBinder callbackInterface.asBinder()}
+ * object is already in the list), then it will be replaced with the new interface.
* Registrations are not counted; a single call to {@link #unregister}
- * will remove a callback after any number calls to register it.
+ * will remove an interface after any number calls to register it.
*
- * @param callback The callback interface to be added to the list. Must
+ * @param callbackInterface The callback interface to be added to the list. Must
* not be null -- passing null here will cause a NullPointerException.
* Most services will want to check for null before calling this with
* an object given from a client, so that clients can't crash the
* service with bad data.
*
* @param cookie Optional additional data to be associated with this
- * callback.
+ * interface.
*
- * @return Returns true if the callback was successfully added to the list.
+ * @return Returns true if the interface was successfully added to the list.
* Returns false if it was not added, either because {@link #kill} had
- * previously been called or the callback's process has gone away.
+ * previously been called or the interface's process has gone away.
*
* @see #unregister
* @see #kill
* @see #onCallbackDied
*/
- public boolean register(E callback, Object cookie) {
- synchronized (mCallbacks) {
+ public boolean register(E callbackInterface, Object cookie) {
+ synchronized (mInterfaces) {
if (mKilled) {
return false;
}
// Flag unusual case that could be caused by a leak. b/36778087
- logExcessiveCallbacks();
- IBinder binder = callback.asBinder();
+ logExcessiveInterfaces();
+ IBinder binder = callbackInterface.asBinder();
try {
- Callback cb = new Callback(callback, cookie);
- unregister(callback);
- binder.linkToDeath(cb, 0);
- mCallbacks.put(binder, cb);
+ Interface i = new Interface(callbackInterface, cookie);
+ unregister(callbackInterface);
+ binder.linkToDeath(i, 0);
+ i.maybeSubscribeToFrozenCallback();
+ mInterfaces.put(binder, i);
return true;
} catch (RemoteException e) {
return false;
@@ -136,27 +375,28 @@
}
/**
- * Remove from the list a callback that was previously added with
+ * Remove from the list an interface that was previously added with
* {@link #register}. This uses the
- * {@link IInterface#asBinder callback.asBinder()} object to correctly
+ * {@link IInterface#asBinder callbackInterface.asBinder()} object to correctly
* find the previous registration.
* Registrations are not counted; a single unregister call will remove
- * a callback after any number calls to {@link #register} for it.
+ * an interface after any number calls to {@link #register} for it.
*
- * @param callback The callback to be removed from the list. Passing
+ * @param callbackInterface The interface to be removed from the list. Passing
* null here will cause a NullPointerException, so you will generally want
* to check for null before calling.
*
- * @return Returns true if the callback was found and unregistered. Returns
- * false if the given callback was not found on the list.
+ * @return Returns true if the interface was found and unregistered. Returns
+ * false if the given interface was not found on the list.
*
* @see #register
*/
- public boolean unregister(E callback) {
- synchronized (mCallbacks) {
- Callback cb = mCallbacks.remove(callback.asBinder());
- if (cb != null) {
- cb.mCallback.asBinder().unlinkToDeath(cb, 0);
+ public boolean unregister(E callbackInterface) {
+ synchronized (mInterfaces) {
+ Interface i = mInterfaces.remove(callbackInterface.asBinder());
+ if (i != null) {
+ i.mInterface.asBinder().unlinkToDeath(i, 0);
+ i.maybeUnsubscribeFromFrozenCallback();
return true;
}
return false;
@@ -164,20 +404,21 @@
}
/**
- * Disable this callback list. All registered callbacks are unregistered,
+ * Disable this interface list. All registered interfaces are unregistered,
* and the list is disabled so that future calls to {@link #register} will
* fail. This should be used when a Service is stopping, to prevent clients
- * from registering callbacks after it is stopped.
+ * from registering interfaces after it is stopped.
*
* @see #register
*/
public void kill() {
- synchronized (mCallbacks) {
- for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
- Callback cb = mCallbacks.valueAt(cbi);
- cb.mCallback.asBinder().unlinkToDeath(cb, 0);
+ synchronized (mInterfaces) {
+ for (int cbi = mInterfaces.size() - 1; cbi >= 0; cbi--) {
+ Interface i = mInterfaces.valueAt(cbi);
+ i.mInterface.asBinder().unlinkToDeath(i, 0);
+ i.maybeUnsubscribeFromFrozenCallback();
}
- mCallbacks.clear();
+ mInterfaces.clear();
mKilled = true;
}
}
@@ -186,15 +427,15 @@
* Old version of {@link #onCallbackDied(E, Object)} that
* does not provide a cookie.
*/
- public void onCallbackDied(E callback) {
+ public void onCallbackDied(E callbackInterface) {
}
/**
- * Called when the process hosting a callback in the list has gone away.
+ * Called when the process hosting an interface in the list has gone away.
* The default implementation calls {@link #onCallbackDied(E)}
* for backwards compatibility.
*
- * @param callback The callback whose process has died. Note that, since
+ * @param callbackInterface The interface whose process has died. Note that, since
* its process has died, you can not make any calls on to this interface.
* You can, however, retrieve its IBinder and compare it with another
* IBinder to see if it is the same object.
@@ -203,13 +444,15 @@
*
* @see #register
*/
- public void onCallbackDied(E callback, Object cookie) {
- onCallbackDied(callback);
+ public void onCallbackDied(E callbackInterface, Object cookie) {
+ onCallbackDied(callbackInterface);
}
/**
- * Prepare to start making calls to the currently registered callbacks.
- * This creates a copy of the callback list, which you can retrieve items
+ * Use {@link #broadcast(Consumer)} instead to ensure proper handling of frozen processes.
+ *
+ * Prepare to start making calls to the currently registered interfaces.
+ * This creates a copy of the interface list, which you can retrieve items
* from using {@link #getBroadcastItem}. Note that only one broadcast can
* be active at a time, so you must be sure to always call this from the
* same thread (usually by scheduling with {@link Handler}) or
@@ -219,44 +462,56 @@
* <p>A typical loop delivering a broadcast looks like this:
*
* <pre>
- * int i = callbacks.beginBroadcast();
+ * int i = interfaces.beginBroadcast();
* while (i > 0) {
* i--;
* try {
- * callbacks.getBroadcastItem(i).somethingHappened();
+ * interfaces.getBroadcastItem(i).somethingHappened();
* } catch (RemoteException e) {
* // The RemoteCallbackList will take care of removing
* // the dead object for us.
* }
* }
- * callbacks.finishBroadcast();</pre>
+ * interfaces.finishBroadcast();</pre>
*
- * @return Returns the number of callbacks in the broadcast, to be used
+ * Note that this method is only supported for {@link #FROZEN_CALLEE_POLICY_UNSET}. For other
+ * policies use {@link #broadcast(Consumer)} instead.
+ *
+ * @return Returns the number of interfaces in the broadcast, to be used
* with {@link #getBroadcastItem} to determine the range of indices you
* can supply.
*
+ * @throws UnsupportedOperationException if an frozen callee policy is set.
+ *
* @see #getBroadcastItem
* @see #finishBroadcast
*/
public int beginBroadcast() {
- synchronized (mCallbacks) {
+ if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) {
+ throw new UnsupportedOperationException();
+ }
+ return beginBroadcastInternal();
+ }
+
+ private int beginBroadcastInternal() {
+ synchronized (mInterfaces) {
if (mBroadcastCount > 0) {
throw new IllegalStateException(
"beginBroadcast() called while already in a broadcast");
}
- final int N = mBroadcastCount = mCallbacks.size();
- if (N <= 0) {
+ final int n = mBroadcastCount = mInterfaces.size();
+ if (n <= 0) {
return 0;
}
Object[] active = mActiveBroadcast;
- if (active == null || active.length < N) {
- mActiveBroadcast = active = new Object[N];
+ if (active == null || active.length < n) {
+ mActiveBroadcast = active = new Object[n];
}
- for (int i=0; i<N; i++) {
- active[i] = mCallbacks.valueAt(i);
+ for (int i = 0; i < n; i++) {
+ active[i] = mInterfaces.valueAt(i);
}
- return N;
+ return n;
}
}
@@ -267,24 +522,23 @@
* calling {@link #finishBroadcast}.
*
* <p>Note that it is possible for the process of one of the returned
- * callbacks to go away before you call it, so you will need to catch
+ * interfaces to go away before you call it, so you will need to catch
* {@link RemoteException} when calling on to the returned object.
- * The callback list itself, however, will take care of unregistering
+ * The interface list itself, however, will take care of unregistering
* these objects once it detects that it is no longer valid, so you can
* handle such an exception by simply ignoring it.
*
- * @param index Which of the registered callbacks you would like to
+ * @param index Which of the registered interfaces you would like to
* retrieve. Ranges from 0 to {@link #beginBroadcast}-1, inclusive.
*
- * @return Returns the callback interface that you can call. This will
- * always be non-null.
+ * @return Returns the interface that you can call. This will always be non-null.
*
* @see #beginBroadcast
*/
public E getBroadcastItem(int index) {
- return ((Callback)mActiveBroadcast[index]).mCallback;
+ return ((Interface) mActiveBroadcast[index]).mInterface;
}
-
+
/**
* Retrieve the cookie associated with the item
* returned by {@link #getBroadcastItem(int)}.
@@ -292,7 +546,7 @@
* @see #getBroadcastItem
*/
public Object getBroadcastCookie(int index) {
- return ((Callback)mActiveBroadcast[index]).mCookie;
+ return ((Interface) mActiveBroadcast[index]).mCookie;
}
/**
@@ -303,7 +557,7 @@
* @see #beginBroadcast
*/
public void finishBroadcast() {
- synchronized (mCallbacks) {
+ synchronized (mInterfaces) {
if (mBroadcastCount < 0) {
throw new IllegalStateException(
"finishBroadcast() called outside of a broadcast");
@@ -322,16 +576,18 @@
}
/**
- * Performs {@code action} on each callback, calling
- * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+ * Performs {@code callback} on each registered interface.
*
- * @hide
+ * This is equivalent to #beginBroadcast, followed by iterating over the items using
+ * #getBroadcastItem and then @finishBroadcast, except that this method supports
+ * frozen callee policies.
*/
- public void broadcast(Consumer<E> action) {
- int itemCount = beginBroadcast();
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public void broadcast(@NonNull Consumer<E> callback) {
+ int itemCount = beginBroadcastInternal();
try {
for (int i = 0; i < itemCount; i++) {
- action.accept(getBroadcastItem(i));
+ ((Interface) mActiveBroadcast[i]).addCallback(callback);
}
} finally {
finishBroadcast();
@@ -339,16 +595,16 @@
}
/**
- * Performs {@code action} for each cookie associated with a callback, calling
+ * Performs {@code callback} for each cookie associated with an interface, calling
* {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
*
* @hide
*/
- public <C> void broadcastForEachCookie(Consumer<C> action) {
+ public <C> void broadcastForEachCookie(Consumer<C> callback) {
int itemCount = beginBroadcast();
try {
for (int i = 0; i < itemCount; i++) {
- action.accept((C) getBroadcastCookie(i));
+ callback.accept((C) getBroadcastCookie(i));
}
} finally {
finishBroadcast();
@@ -356,16 +612,16 @@
}
/**
- * Performs {@code action} on each callback and associated cookie, calling {@link
+ * Performs {@code callback} on each interface and associated cookie, calling {@link
* #beginBroadcast()}/{@link #finishBroadcast()} before/after looping.
*
* @hide
*/
- public <C> void broadcast(BiConsumer<E, C> action) {
+ public <C> void broadcast(BiConsumer<E, C> callback) {
int itemCount = beginBroadcast();
try {
for (int i = 0; i < itemCount; i++) {
- action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i));
+ callback.accept(getBroadcastItem(i), (C) getBroadcastCookie(i));
}
} finally {
finishBroadcast();
@@ -373,10 +629,10 @@
}
/**
- * Returns the number of registered callbacks. Note that the number of registered
- * callbacks may differ from the value returned by {@link #beginBroadcast()} since
- * the former returns the number of callbacks registered at the time of the call
- * and the second the number of callback to which the broadcast will be delivered.
+ * Returns the number of registered interfaces. Note that the number of registered
+ * interfaces may differ from the value returned by {@link #beginBroadcast()} since
+ * the former returns the number of interfaces registered at the time of the call
+ * and the second the number of interfaces to which the broadcast will be delivered.
* <p>
* This function is useful to decide whether to schedule a broadcast if this
* requires doing some work which otherwise would not be performed.
@@ -385,39 +641,39 @@
* @return The size.
*/
public int getRegisteredCallbackCount() {
- synchronized (mCallbacks) {
+ synchronized (mInterfaces) {
if (mKilled) {
return 0;
}
- return mCallbacks.size();
+ return mInterfaces.size();
}
}
/**
- * Return a currently registered callback. Note that this is
+ * Return a currently registered interface. Note that this is
* <em>not</em> the same as {@link #getBroadcastItem} and should not be used
- * interchangeably with it. This method returns the registered callback at the given
+ * interchangeably with it. This method returns the registered interface at the given
* index, not the current broadcast state. This means that it is not itself thread-safe:
* any call to {@link #register} or {@link #unregister} will change these indices, so you
* must do your own thread safety between these to protect from such changes.
*
- * @param index Index of which callback registration to return, from 0 to
+ * @param index Index of which interface registration to return, from 0 to
* {@link #getRegisteredCallbackCount()} - 1.
*
- * @return Returns whatever callback is associated with this index, or null if
+ * @return Returns whatever interface is associated with this index, or null if
* {@link #kill()} has been called.
*/
public E getRegisteredCallbackItem(int index) {
- synchronized (mCallbacks) {
+ synchronized (mInterfaces) {
if (mKilled) {
return null;
}
- return mCallbacks.valueAt(index).mCallback;
+ return mInterfaces.valueAt(index).mInterface;
}
}
/**
- * Return any cookie associated with a currently registered callback. Note that this is
+ * Return any cookie associated with a currently registered interface. Note that this is
* <em>not</em> the same as {@link #getBroadcastCookie} and should not be used
* interchangeably with it. This method returns the current cookie registered at the given
* index, not the current broadcast state. This means that it is not itself thread-safe:
@@ -431,25 +687,25 @@
* {@link #kill()} has been called.
*/
public Object getRegisteredCallbackCookie(int index) {
- synchronized (mCallbacks) {
+ synchronized (mInterfaces) {
if (mKilled) {
return null;
}
- return mCallbacks.valueAt(index).mCookie;
+ return mInterfaces.valueAt(index).mCookie;
}
}
/** @hide */
public void dump(PrintWriter pw, String prefix) {
- synchronized (mCallbacks) {
- pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size());
+ synchronized (mInterfaces) {
+ pw.print(prefix); pw.print("callbacks: "); pw.println(mInterfaces.size());
pw.print(prefix); pw.print("killed: "); pw.println(mKilled);
pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount);
}
}
- private void logExcessiveCallbacks() {
- final long size = mCallbacks.size();
+ private void logExcessiveInterfaces() {
+ final long size = mInterfaces.size();
final long TOO_MANY = 3000;
final long MAX_CHARS = 1000;
if (size >= TOO_MANY) {
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 81987907..c7cc653 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -2,87 +2,7 @@
container: "system"
container: "system"
-flag {
- name: "android_os_build_vanilla_ice_cream"
- is_exported: true
- namespace: "build"
- description: "Feature flag for adding the VANILLA_ICE_CREAM constant."
- bug: "264658905"
-}
-
-flag {
- name: "state_of_health_public"
- is_exported: true
- namespace: "system_sw_battery"
- description: "Feature flag for making state_of_health a public api."
- bug: "288842045"
-}
-
-flag {
- name: "disallow_cellular_null_ciphers_restriction"
- namespace: "cellular_security"
- description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
- bug: "276752881"
-}
-
-flag {
- name: "remove_app_profiler_pss_collection"
- is_exported: true
- namespace: "backstage_power"
- description: "Replaces background PSS collection in AppProfiler with RSS"
- bug: "297542292"
-}
-
-flag {
- name: "allow_thermal_headroom_thresholds"
- is_exported: true
- namespace: "game"
- description: "Enable thermal headroom thresholds API"
- bug: "288119641"
-}
-
-# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
-flag {
- name: "allow_private_profile"
- is_exported: true
- namespace: "profile_experiences"
- description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
- bug: "299069460"
- is_exported: true
-}
-
-flag {
- name: "adpf_prefer_power_efficiency"
- is_exported: true
- namespace: "game"
- description: "Guards the ADPF power efficiency API"
- bug: "288117936"
-}
-
-flag {
- name: "security_state_service"
- is_exported: true
- namespace: "dynamic_spl"
- description: "Guards the Security State API."
- bug: "302189431"
-}
-
-flag {
- name: "ordered_broadcast_multiple_permissions"
- is_exported: true
- namespace: "bluetooth"
- description: "Guards the Context.sendOrderedBroadcastMultiplePermissions API"
- bug: "345802719"
-}
-
-flag {
- name: "battery_saver_supported_check_api"
- is_exported: true
- namespace: "backstage_power"
- description: "Guards a new API in PowerManager to check if battery saver is supported or not."
- bug: "305067031"
-}
-
+# keep-sorted start block=yes newline_separated=yes
flag {
name: "adpf_gpu_report_actual_work_duration"
is_exported: true
@@ -92,21 +12,6 @@
}
flag {
- name: "adpf_use_fmq_channel"
- namespace: "game"
- description: "Guards use of the FMQ channel for ADPF"
- bug: "315894228"
-}
-
-flag {
- name: "adpf_use_fmq_channel_fixed"
- namespace: "game"
- description: "Guards use of the FMQ channel for ADPF with a readonly flag"
- is_fixed_read_only: true
- bug: "315894228"
-}
-
-flag {
name: "adpf_hwui_gpu"
namespace: "game"
description: "Guards use of the FMQ channel for ADPF"
@@ -115,6 +20,13 @@
}
flag {
+ name: "adpf_measure_during_input_event_boost"
+ namespace: "game"
+ description: "Guards use of a boost when view measures during input events"
+ bug: "256549451"
+}
+
+flag {
name: "adpf_obtainview_boost"
namespace: "game"
description: "Guards use of a boost in response to HWUI obtainView"
@@ -131,41 +43,67 @@
}
flag {
- name: "adpf_measure_during_input_event_boost"
- namespace: "game"
- description: "Guards use of a boost when view measures during input events"
- bug: "256549451"
-}
-
-flag {
- name: "battery_service_support_current_adb_command"
- namespace: "backstage_power"
- description: "Whether or not BatteryService supports adb commands for Current values."
- is_fixed_read_only: true
- bug: "315037695"
-}
-
-flag {
- name: "strict_mode_restricted_network"
- namespace: "backstage_power"
- description: "Guards StrictMode APIs for detecting restricted network access."
- bug: "317250784"
-}
-
-flag {
- name: "binder_frozen_state_change_callback"
+ name: "adpf_prefer_power_efficiency"
is_exported: true
- namespace: "system_performance"
- description: "Guards the frozen state change callback API."
- bug: "361157077"
+ namespace: "game"
+ description: "Guards the ADPF power efficiency API"
+ bug: "288117936"
}
flag {
- name: "message_queue_tail_tracking"
- namespace: "system_performance"
- description: "track tail of message queue."
- bug: "305311707"
+ name: "adpf_use_fmq_channel"
+ namespace: "game"
+ description: "Guards use of the FMQ channel for ADPF"
+ bug: "315894228"
+}
+
+flag {
+ name: "adpf_use_fmq_channel_fixed"
+ namespace: "game"
+ description: "Guards use of the FMQ channel for ADPF with a readonly flag"
is_fixed_read_only: true
+ bug: "315894228"
+}
+
+flag {
+ name: "allow_consentless_bugreport_delegated_consent"
+ namespace: "crumpet"
+ description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
+ bug: "324046728"
+}
+
+# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
+flag {
+ name: "allow_private_profile"
+ is_exported: true
+ namespace: "profile_experiences"
+ description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
+ bug: "299069460"
+ is_exported: true
+}
+
+flag {
+ name: "allow_thermal_headroom_thresholds"
+ is_exported: true
+ namespace: "game"
+ description: "Enable thermal headroom thresholds API"
+ bug: "288119641"
+}
+
+flag {
+ name: "android_os_build_vanilla_ice_cream"
+ is_exported: true
+ namespace: "build"
+ description: "Feature flag for adding the VANILLA_ICE_CREAM constant."
+ bug: "264658905"
+}
+
+flag {
+ name: "api_for_backported_fixes"
+ namespace: "media_reliability"
+ description: "Public API app developers use to check if a known issue is fixed on a device."
+ bug: "308461809"
+ is_exported: true
}
flag {
@@ -178,35 +116,42 @@
}
flag {
- name: "storage_lifetime_api"
+ name: "battery_saver_supported_check_api"
is_exported: true
- namespace: "phoenix"
- description: "Feature flag for adding storage component health APIs."
+ namespace: "backstage_power"
+ description: "Guards a new API in PowerManager to check if battery saver is supported or not."
+ bug: "305067031"
+}
+
+flag {
+ name: "battery_service_support_current_adb_command"
+ namespace: "backstage_power"
+ description: "Whether or not BatteryService supports adb commands for Current values."
is_fixed_read_only: true
- bug: "309792384"
+ bug: "315037695"
}
flag {
- namespace: "system_performance"
- name: "telemetry_apis_framework_initialization"
- is_exported: true
- description: "Control framework initialization APIs of telemetry APIs feature."
- is_fixed_read_only: true
- bug: "324241334"
+ name: "binder_frozen_state_change_callback"
+ is_exported: true
+ namespace: "system_performance"
+ description: "Guards the frozen state change callback API."
+ bug: "361157077"
}
flag {
- namespace: "system_performance"
- name: "perfetto_sdk_tracing"
- description: "Tracing using Perfetto SDK."
- bug: "303199244"
+ name: "disallow_cellular_null_ciphers_restriction"
+ namespace: "cellular_security"
+ description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
+ bug: "276752881"
}
flag {
- name: "allow_consentless_bugreport_delegated_consent"
- namespace: "crumpet"
- description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
- bug: "324046728"
+ name: "enable_angle_allow_list"
+ namespace: "gpu"
+ description: "Whether to read from angle allowlist to determine if app should use ANGLE"
+ is_fixed_read_only: true
+ bug: "370845648"
}
flag {
@@ -226,6 +171,14 @@
}
flag {
+ name: "message_queue_tail_tracking"
+ namespace: "system_performance"
+ description: "track tail of message queue."
+ bug: "305311707"
+ is_fixed_read_only: true
+}
+
+flag {
name: "network_time_uses_shared_memory"
namespace: "system_performance"
description: "SystemClock.currentNetworkTimeMillis() reads network time offset from shared memory"
@@ -234,17 +187,67 @@
}
flag {
- name: "enable_angle_allow_list"
- namespace: "gpu"
- description: "Whether to read from angle allowlist to determine if app should use ANGLE"
- is_fixed_read_only: true
- bug: "370845648"
+ name: "ordered_broadcast_multiple_permissions"
+ is_exported: true
+ namespace: "bluetooth"
+ description: "Guards the Context.sendOrderedBroadcastMultiplePermissions API"
+ bug: "345802719"
}
flag {
- name: "api_for_backported_fixes"
- namespace: "media_reliability"
- description: "Public API app developers use to check if a known issue is fixed on a device."
- bug: "308461809"
+ name: "remove_app_profiler_pss_collection"
is_exported: true
+ namespace: "backstage_power"
+ description: "Replaces background PSS collection in AppProfiler with RSS"
+ bug: "297542292"
}
+
+flag {
+ name: "security_state_service"
+ is_exported: true
+ namespace: "dynamic_spl"
+ description: "Guards the Security State API."
+ bug: "302189431"
+}
+
+flag {
+ name: "state_of_health_public"
+ is_exported: true
+ namespace: "system_sw_battery"
+ description: "Feature flag for making state_of_health a public api."
+ bug: "288842045"
+}
+
+flag {
+ name: "storage_lifetime_api"
+ is_exported: true
+ namespace: "phoenix"
+ description: "Feature flag for adding storage component health APIs."
+ is_fixed_read_only: true
+ bug: "309792384"
+}
+
+flag {
+ name: "strict_mode_restricted_network"
+ namespace: "backstage_power"
+ description: "Guards StrictMode APIs for detecting restricted network access."
+ bug: "317250784"
+}
+
+flag {
+ namespace: "system_performance"
+ name: "perfetto_sdk_tracing"
+ description: "Tracing using Perfetto SDK."
+ bug: "303199244"
+}
+
+flag {
+ namespace: "system_performance"
+ name: "telemetry_apis_framework_initialization"
+ is_exported: true
+ description: "Control framework initialization APIs of telemetry APIs feature."
+ is_fixed_read_only: true
+ bug: "324241334"
+}
+
+# keep-sorted end
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 06621c9..8bd7078 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -430,7 +430,6 @@
} // namespace android
-#ifndef _WIN32
using namespace android;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -439,12 +438,14 @@
return JNI_ERR;
}
- Vector<String8> args;
- HostRuntime runtime;
+ string useBaseHostRuntime = getJavaProperty(env, "use_base_native_hostruntime");
+ if (useBaseHostRuntime == "true") {
+ Vector<String8> args;
+ HostRuntime runtime;
- runtime.onVmCreated(env);
- runtime.start("HostRuntime", args, false);
+ runtime.onVmCreated(env);
+ runtime.start("HostRuntime", args, false);
+ }
return JNI_VERSION_1_6;
}
-#endif
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 7f1e4a8..61cd1c3 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -170,7 +170,7 @@
SNAP_TO_NONE,
SNAP_TO_START_AND_DISMISS,
SNAP_TO_END_AND_DISMISS,
- SNAP_TO_MINIMIZE
+ SNAP_TO_MINIMIZE,
})
public @interface SnapPosition {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 92535f3..57a59c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -521,7 +521,6 @@
wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
-
}
private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
@@ -663,7 +662,6 @@
wct.reparent(task.token, displayAreaInfo.token, true /* onTop */)
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
-
}
/** Moves a task in/out of full immersive state within the desktop. */
@@ -739,7 +737,6 @@
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
-
}
private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
@@ -851,7 +848,6 @@
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds)
-
}
@VisibleForTesting
@@ -1246,10 +1242,23 @@
error("Invalid windowing mode: ${callingTask.windowingMode}")
}
}
+ val bounds = when (newTaskWindowingMode) {
+ WINDOWING_MODE_FREEFORM -> {
+ displayController.getDisplayLayout(callingTask.displayId)
+ ?.let { getInitialBounds(it, callingTask) }
+ }
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ Rect()
+ }
+ else -> {
+ error("Invalid windowing mode: $newTaskWindowingMode")
+ }
+ }
return ActivityOptions.makeBasic().apply {
launchWindowingMode = newTaskWindowingMode
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+ launchBounds = bounds
}
}
@@ -1405,15 +1414,7 @@
} else {
WINDOWING_MODE_FREEFORM
}
- val initialBounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
- calculateInitialBounds(displayLayout, taskInfo)
- } else {
- getDefaultDesktopTaskBounds(displayLayout)
- }
-
- if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue()) {
- cascadeWindow(taskInfo, initialBounds, displayLayout)
- }
+ val initialBounds = getInitialBounds(displayLayout, taskInfo)
if (canChangeTaskPosition(taskInfo)) {
wct.setBounds(taskInfo.token, initialBounds)
@@ -1425,6 +1426,22 @@
}
}
+ private fun getInitialBounds(
+ displayLayout: DisplayLayout,
+ taskInfo: RunningTaskInfo
+ ): Rect {
+ val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
+ calculateInitialBounds(displayLayout, taskInfo)
+ } else {
+ getDefaultDesktopTaskBounds(displayLayout)
+ }
+
+ if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
+ cascadeWindow(taskInfo, bounds, displayLayout)
+ }
+ return bounds
+ }
+
private fun addMoveToFullscreenChanges(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index dfa2437..5c72cb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -23,6 +23,7 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
@@ -43,14 +44,18 @@
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.view.DragEvent;
import android.view.SurfaceControl;
+import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.window.WindowContainerToken;
@@ -67,14 +72,22 @@
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiConsumer;
/**
* Coordinates the visible drop targets for the current drag within a single display.
*/
public class DragLayout extends LinearLayout
- implements ViewTreeObserver.OnComputeInternalInsetsListener, DragLayoutProvider {
+ implements ViewTreeObserver.OnComputeInternalInsetsListener, DragLayoutProvider,
+ DragZoneAnimator{
+ static final boolean DEBUG_LAYOUT = false;
// While dragging the status bar is hidden.
private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
| StatusBarManager.DISABLE_NOTIFICATION_ALERTS
@@ -108,13 +121,19 @@
// The last position that was handled by the drag layout
private final Point mLastPosition = new Point();
+ // Used with enableFlexibleSplit() flag
+ private List<SplitDragPolicy.Target> mTargets;
+ private Map<SplitDragPolicy.Target, DropZoneView> mTargetDropMap = new HashMap<>();
+ private FrameLayout mAnimatingRootLayout;
+ // Used with enableFlexibleSplit() flag
+
@SuppressLint("WrongConstant")
public DragLayout(Context context, SplitScreenController splitScreenController,
IconProvider iconProvider) {
super(context);
mSplitScreenController = splitScreenController;
mIconProvider = iconProvider;
- mPolicy = new SplitDragPolicy(context, splitScreenController);
+ mPolicy = new SplitDragPolicy(context, splitScreenController, this);
mStatusBarManager = context.getSystemService(StatusBarManager.class);
mLastConfiguration.setTo(context.getResources().getConfiguration());
@@ -211,11 +230,26 @@
boolean isLeftRightSplit = mSplitScreenController != null
&& mSplitScreenController.isLeftRightSplit();
if (isLeftRightSplit) {
- mDropZoneView1.setBottomInset(mInsets.bottom);
- mDropZoneView2.setBottomInset(mInsets.bottom);
+ if (enableFlexibleSplit()) {
+ mTargetDropMap.values().forEach(dzv -> dzv.setBottomInset(mInsets.bottom));
+ } else {
+ mDropZoneView1.setBottomInset(mInsets.bottom);
+ mDropZoneView2.setBottomInset(mInsets.bottom);
+ }
} else {
- mDropZoneView1.setBottomInset(0);
- mDropZoneView2.setBottomInset(mInsets.bottom);
+ if (enableFlexibleSplit()) {
+ Collection<DropZoneView> dropViews = mTargetDropMap.values();
+ final DropZoneView[] bottomView = {null};
+ dropViews.forEach(dropZoneView -> {
+ bottomView[0] = dropZoneView;
+ dropZoneView.setBottomInset(0);
+ });
+ // TODO(b/349828130): necessary? maybe with UI polish
+ // bottomView[0].setBottomInset(mInsets.bottom);
+ } else {
+ mDropZoneView1.setBottomInset(0);
+ mDropZoneView2.setBottomInset(mInsets.bottom);
+ }
}
return super.onApplyWindowInsets(insets);
}
@@ -233,17 +267,31 @@
final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
|| (diff & CONFIG_UI_MODE) != 0;
if (themeChanged) {
- mDropZoneView1.onThemeChange();
- mDropZoneView2.onThemeChange();
+ if (enableFlexibleSplit()) {
+ mTargetDropMap.values().forEach(DropZoneView::onThemeChange);
+ } else {
+ mDropZoneView1.onThemeChange();
+ mDropZoneView2.onThemeChange();
+ }
}
mLastConfiguration.setTo(newConfig);
requestLayout();
}
private void updateContainerMarginsForSingleTask() {
- mDropZoneView1.setContainerMargin(
- mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
- mDropZoneView2.setContainerMargin(0, 0, 0, 0);
+ if (enableFlexibleSplit()) {
+ DropZoneView firstDropZone = mTargetDropMap.values().stream().findFirst().get();
+ mTargetDropMap.values().stream()
+ .filter(dropZoneView -> dropZoneView != firstDropZone)
+ .forEach(dropZoneView -> dropZoneView.setContainerMargin(0, 0, 0, 0));
+ firstDropZone.setContainerMargin(
+ mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin
+ );
+ } else {
+ mDropZoneView1.setContainerMargin(
+ mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
+ mDropZoneView2.setContainerMargin(0, 0, 0, 0);
+ }
}
private void updateContainerMargins(boolean isLeftRightSplit) {
@@ -306,19 +354,35 @@
}
}
} else {
- // We're already in split so get taskInfo from the controller to populate icon / color.
- ActivityManager.RunningTaskInfo topOrLeftTask =
- mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- ActivityManager.RunningTaskInfo bottomOrRightTask =
- mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- if (topOrLeftTask != null && bottomOrRightTask != null) {
- Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
- int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
- Drawable bottomOrRightIcon = mIconProvider.getIcon(
- bottomOrRightTask.topActivityInfo);
- int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
- mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
- mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
+ ActivityManager.RunningTaskInfo[] taskInfos = mSplitScreenController.getAllTaskInfos();
+ boolean anyTasksNull = Arrays.stream(taskInfos).anyMatch(Objects::isNull);
+ if (enableFlexibleSplit() && taskInfos != null && !anyTasksNull) {
+ int i = 0;
+ for (DropZoneView v : mTargetDropMap.values()) {
+ if (i >= taskInfos.length) {
+ // TODO(b/349828130) Support once we add 3 StageRoots
+ continue;
+ }
+ ActivityManager.RunningTaskInfo task = taskInfos[i];
+ v.setAppInfo(getResizingBackgroundColor(task),
+ mIconProvider.getIcon(task.topActivityInfo));
+ i++;
+ }
+ } else {
+ // We're already in split so get taskInfo from the controller to populate icon / color.
+ ActivityManager.RunningTaskInfo topOrLeftTask =
+ mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ ActivityManager.RunningTaskInfo bottomOrRightTask =
+ mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ if (topOrLeftTask != null && bottomOrRightTask != null) {
+ Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
+ int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
+ Drawable bottomOrRightIcon = mIconProvider.getIcon(
+ bottomOrRightTask.topActivityInfo);
+ int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
+ mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
+ mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
+ }
}
// Update the dropzones to match existing split sizes
@@ -391,7 +455,14 @@
@NonNull
@Override
public void addDraggingView(ViewGroup rootView) {
- // TODO(b/349828130) We need to separate out view + logic here
+ if (enableFlexibleSplit()) {
+ removeAllViews();
+ mAnimatingRootLayout = new FrameLayout(getContext());
+ addView(mAnimatingRootLayout,
+ new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ ((LayoutParams) mAnimatingRootLayout.getLayoutParams()).weight = 1;
+ }
+
rootView.addView(this, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@@ -409,6 +480,24 @@
// Inset the draw region by a little bit
target.drawRegion.inset(mDisplayMargin, mDisplayMargin);
}
+
+ if (enableFlexibleSplit()) {
+ mTargets = targets;
+ mTargetDropMap.clear();
+ for (int i = 0; i < mTargets.size(); i++) {
+ DropZoneView v = new DropZoneView(getContext());
+ SplitDragPolicy.Target t = mTargets.get(i);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(t.drawRegion.width(),
+ t.drawRegion.height());
+ mAnimatingRootLayout.addView(v, params);
+ v.setTranslationX(t.drawRegion.left);
+ v.setTranslationY(t.drawRegion.top);
+ mTargetDropMap.put(t, v);
+ if (DEBUG_LAYOUT) {
+ v.setDebugIndex(t.index);
+ }
+ }
+ }
}
/**
@@ -433,6 +522,9 @@
if (target == null) {
// Animating to no target
animateSplitContainers(false, null /* animCompleteCallback */);
+ if (enableFlexibleSplit()) {
+ animateHighlight(target);
+ }
} else if (mCurrentTarget == null) {
if (mPolicy.getNumTargets() == 1) {
animateFullscreenContainer(true);
@@ -440,10 +532,14 @@
animateSplitContainers(true, null /* animCompleteCallback */);
animateHighlight(target);
}
- } else if (mCurrentTarget.type != target.type) {
+ } else if (mCurrentTarget.type != target.type || enableFlexibleSplit()) {
// Switching between targets
- mDropZoneView1.animateSwitch();
- mDropZoneView2.animateSwitch();
+ if (enableFlexibleSplit()) {
+ animateHighlight(target);
+ } else {
+ mDropZoneView1.animateSwitch();
+ mDropZoneView2.animateSwitch();
+ }
// Announce for accessibility.
switch (target.type) {
case TYPE_SPLIT_LEFT:
@@ -490,6 +586,9 @@
mDropZoneView2.setForceIgnoreBottomMargin(false);
updateContainerMargins(mIsLeftRightSplit);
mCurrentTarget = null;
+ if (enableFlexibleSplit()) {
+ mAnimatingRootLayout.removeAllViews();
+ }
}
/**
@@ -566,9 +665,20 @@
mStatusBarManager.disable(visible
? HIDE_STATUS_BAR_FLAGS
: DISABLE_NONE);
- mDropZoneView1.setShowingMargin(visible);
- mDropZoneView2.setShowingMargin(visible);
- Animator animator = mDropZoneView1.getAnimator();
+ Animator animator;
+ if (enableFlexibleSplit()) {
+ DropZoneView anyDropZoneView = null;
+ for (DropZoneView dz : mTargetDropMap.values()) {
+ dz.setShowingMargin(visible);
+ anyDropZoneView = dz;
+ }
+ animator = anyDropZoneView != null ? anyDropZoneView.getAnimator() : null;
+ } else {
+ mDropZoneView1.setShowingMargin(visible);
+ mDropZoneView2.setShowingMargin(visible);
+ animator = mDropZoneView1.getAnimator();
+ }
+
if (animCompleteCallback != null) {
if (animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@@ -584,7 +694,24 @@
}
}
+ @Override
+ public void animateDragTargets(
+ @NonNull List<? extends BiConsumer<SplitDragPolicy.Target, View>> viewsToAnimate) {
+ for (Map.Entry<SplitDragPolicy.Target, DropZoneView> entry : mTargetDropMap.entrySet()) {
+ viewsToAnimate.get(0).accept(entry.getKey(), entry.getValue());
+ }
+ }
+
private void animateHighlight(SplitDragPolicy.Target target) {
+ if (enableFlexibleSplit()) {
+ for (Map.Entry<SplitDragPolicy.Target, DropZoneView> dzv : mTargetDropMap.entrySet()) {
+ // Highlight the view w/ the matching target, unhighlight the rest
+ dzv.getValue().setShowingHighlight(dzv.getKey() == target);
+ }
+ mPolicy.onHoveringOver(target);
+ return;
+ }
+
if (target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP) {
mDropZoneView1.setShowingHighlight(true);
mDropZoneView2.setShowingHighlight(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragZoneAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragZoneAnimator.kt
new file mode 100644
index 0000000..240465d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragZoneAnimator.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop
+
+import android.view.View
+import java.util.function.BiConsumer
+
+interface DragZoneAnimator {
+ /**
+ * Each consumer will be called for the corresponding DropZoneView.
+ * This must match the number of targets in [.mTargets] otherwise will
+ * throw an [IllegalStateException]
+ */
+ fun animateDragTargets(viewsToAnimate: List<BiConsumer<SplitDragPolicy.Target, View>>)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt
index 122a105..2bbca48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropTarget.kt
@@ -47,7 +47,7 @@
/**
* Called when user is hovering Drag object over the given Target
*/
- fun onHoveringOver(target: SplitDragPolicy.Target) {}
+ fun onHoveringOver(target: SplitDragPolicy.Target?) {}
/**
* Called when the user has dropped the provided target (need not be the same target as
* [onHoveringOver])
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index f9749ec..e503b8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -20,12 +20,14 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.Gravity;
@@ -33,6 +35,7 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.TextView;
import androidx.annotation.Nullable;
@@ -83,6 +86,7 @@
private int mTargetBackgroundColor;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
+ private TextView mDebugIndex;
// Renders a highlight or neutral transparent color
private ColorDrawable mColorDrawable;
@@ -125,6 +129,22 @@
mMarginView = new MarginView(context);
addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
+
+ if (DEBUG_LAYOUT) {
+ mDebugIndex = new TextView(context);
+ mDebugIndex.setVisibility(GONE);
+ mDebugIndex.setTextColor(Color.YELLOW);
+ addView(mDebugIndex, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP));
+
+ View borderView = new View(context);
+ addView(borderView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ GradientDrawable border = new GradientDrawable();
+ border.setShape(GradientDrawable.RECTANGLE);
+ border.setStroke(5, Color.RED);
+ borderView.setBackground(border);
+ }
}
public void onThemeChange() {
@@ -236,6 +256,16 @@
}
}
+ @SuppressLint("SetTextI18n")
+ public void setDebugIndex(int index) {
+ if (!DEBUG_LAYOUT) {
+ return;
+ }
+
+ mDebugIndex.setText("Index:\n" + index);
+ mDebugIndex.setVisibility(VISIBLE);
+ }
+
private void animateBackground(int startColor, int endColor) {
if (DEBUG_LAYOUT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
index 2a19d65..5d22c1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
@@ -32,16 +32,22 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
+import static com.android.wm.shell.draganddrop.DragLayout.DEBUG_LAYOUT;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_FULLSCREEN;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP;
import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
@@ -59,6 +65,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.view.View;
import android.window.WindowContainerToken;
import androidx.annotation.IntDef;
@@ -69,13 +76,23 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.draganddrop.anim.DropTargetAnimSupplier;
+import com.android.wm.shell.draganddrop.anim.HoverAnimProps;
+import com.android.wm.shell.draganddrop.anim.TwoFiftyFiftyTargetAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+import kotlin.Pair;
/**
* The policy for handling drag and drop operations to shell.
@@ -89,24 +106,42 @@
private final Starter mFullscreenStarter;
// Used for launching tasks into splitscreen
private final Starter mSplitscreenStarter;
+ private final DragZoneAnimator mDragZoneAnimator;
private final SplitScreenController mSplitScreen;
- private final ArrayList<SplitDragPolicy.Target> mTargets = new ArrayList<>();
+ private ArrayList<SplitDragPolicy.Target> mTargets = new ArrayList<>();
private final RectF mDisallowHitRegion = new RectF();
+ /**
+ * Maps a given SnapPosition to an array where each index of the array represents one
+ * of the targets that are being hovered over, in order (Left to Right, Top to Bottom).
+ * Ex: 4 drop targets when we're in 50/50 split
+ * 2_50_50 => [ [AnimPropsTarget1, AnimPropsTarget2, AnimPropsTarget3, AnimPropsTarget4],
+ * ... // hovering over target 2,
+ * ... // hovering over target 3,
+ * ... // hovering over target 4
+ * ]
+ */
+ private final Map<Integer, List<List<HoverAnimProps>>> mHoverAnimProps = new HashMap();
private InstanceId mLoggerSessionId;
private DragSession mSession;
+ @Nullable
+ private Target mCurrentHoverTarget;
+ /** This variable is a temporary placeholder, will be queried on drag start. */
+ private int mCurrentSnapPosition = -1;
- public SplitDragPolicy(Context context, SplitScreenController splitScreen) {
- this(context, splitScreen, new DefaultStarter(context));
+ public SplitDragPolicy(Context context, SplitScreenController splitScreen,
+ DragZoneAnimator dragZoneAnimator) {
+ this(context, splitScreen, new DefaultStarter(context), dragZoneAnimator);
}
@VisibleForTesting
SplitDragPolicy(Context context, SplitScreenController splitScreen,
- Starter fullscreenStarter) {
+ Starter fullscreenStarter, DragZoneAnimator dragZoneAnimator) {
mContext = context;
mSplitScreen = splitScreen;
mFullscreenStarter = fullscreenStarter;
mSplitscreenStarter = splitScreen;
+ mDragZoneAnimator = dragZoneAnimator;
}
/**
@@ -164,58 +199,123 @@
|| (mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
&& mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN);
if (allowSplit) {
- // Already split, allow replacing existing split task
- final Rect topOrLeftBounds = new Rect();
- final Rect bottomOrRightBounds = new Rect();
- mSplitScreen.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
- topOrLeftBounds.intersect(displayRegion);
- bottomOrRightBounds.intersect(displayRegion);
+ if (enableFlexibleSplit()) {
+ // TODO(b/349828130) get this from split screen controller, expose the SnapTarget object
+ // entirely and then pull out the SnapPosition
+ @SplitScreenConstants.SnapPosition int snapPosition = SNAP_TO_2_50_50;
+ final Rect startHitRegion = new Rect();
+ final Rect endHitRegion = new Rect();
+ if (!inSplitScreen) {
+ // Currently in fullscreen, split in half
+ final Rect startBounds = new Rect();
+ final Rect endBounds = new Rect();
+ mSplitScreen.getStageBounds(startBounds, endBounds);
+ startBounds.intersect(displayRegion);
+ endBounds.intersect(displayRegion);
- if (isLeftRightSplit) {
- final Rect leftHitRegion = new Rect();
- final Rect rightHitRegion = new Rect();
+ if (isLeftRightSplit) {
+ displayRegion.splitVertically(startHitRegion, endHitRegion);
+ } else {
+ displayRegion.splitHorizontally(startHitRegion, endHitRegion);
+ }
- // If we have existing split regions use those bounds, otherwise split it 50/50
- if (inSplitScreen) {
- // The bounds of the existing split will have a divider bar, the hit region
- // should include that space. Find the center of the divider bar:
- float centerX = topOrLeftBounds.right + (dividerWidth / 2);
- // Now set the hit regions using that center.
- leftHitRegion.set(displayRegion);
- leftHitRegion.right = (int) centerX;
- rightHitRegion.set(displayRegion);
- rightHitRegion.left = (int) centerX;
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, startHitRegion, startBounds, -1));
+ mTargets.add(new Target(TYPE_SPLIT_RIGHT, endHitRegion, endBounds, -1));
} else {
- displayRegion.splitVertically(leftHitRegion, rightHitRegion);
+ // TODO(b/349828130), move this into init function and/or the insets updating
+ // callback
+ DropTargetAnimSupplier supplier = null;
+ switch (snapPosition) {
+ case SNAP_TO_2_50_50:
+ supplier = new TwoFiftyFiftyTargetAnimator();
+ break;
+ case SplitScreenConstants.SNAP_TO_2_33_66:
+ break;
+ case SplitScreenConstants.SNAP_TO_2_66_33:
+ break;
+ case SplitScreenConstants.SNAP_TO_END_AND_DISMISS:
+ break;
+ case SplitScreenConstants.SNAP_TO_MINIMIZE:
+ break;
+ case SplitScreenConstants.SNAP_TO_NONE:
+ break;
+ case SplitScreenConstants.SNAP_TO_START_AND_DISMISS:
+ break;
+ default:
+ }
+
+ Pair<List<Target>, List<List<HoverAnimProps>>> targetsAnims =
+ supplier.getTargets(mSession.displayLayout,
+ insets, isLeftRightSplit, mContext.getResources());
+ mTargets = new ArrayList<>(targetsAnims.getFirst());
+ mHoverAnimProps.put(SNAP_TO_2_50_50, targetsAnims.getSecond());
+ assert(mTargets.size() == targetsAnims.getSecond().size());
+ if (DEBUG_LAYOUT) {
+ for (List<HoverAnimProps> props : targetsAnims.getSecond()) {
+ StringBuilder sb = new StringBuilder();
+ for (HoverAnimProps hap : props) {
+ sb.append(hap).append("\n");
+ }
+ sb.append("\n");
+ Log.d(TAG, sb.toString());
+ }
+ }
}
-
- mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds));
- mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds));
-
} else {
- final Rect topHitRegion = new Rect();
- final Rect bottomHitRegion = new Rect();
+ // Already split, allow replacing existing split task
+ final Rect topOrLeftBounds = new Rect();
+ final Rect bottomOrRightBounds = new Rect();
+ mSplitScreen.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
+ topOrLeftBounds.intersect(displayRegion);
+ bottomOrRightBounds.intersect(displayRegion);
- // If we have existing split regions use those bounds, otherwise split it 50/50
- if (inSplitScreen) {
- // The bounds of the existing split will have a divider bar, the hit region
- // should include that space. Find the center of the divider bar:
- float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
- // Now set the hit regions using that center.
- topHitRegion.set(displayRegion);
- topHitRegion.bottom = (int) centerX;
- bottomHitRegion.set(displayRegion);
- bottomHitRegion.top = (int) centerX;
+ if (isLeftRightSplit) {
+ final Rect leftHitRegion = new Rect();
+ final Rect rightHitRegion = new Rect();
+
+ // If we have existing split regions use those bounds, otherwise split it 50/50
+ if (inSplitScreen) {
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.right + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ leftHitRegion.set(displayRegion);
+ leftHitRegion.right = (int) centerX;
+ rightHitRegion.set(displayRegion);
+ rightHitRegion.left = (int) centerX;
+ } else {
+ displayRegion.splitVertically(leftHitRegion, rightHitRegion);
+ }
+
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds, -1));
+ mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds,
+ -1));
} else {
- displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
- }
+ final Rect topHitRegion = new Rect();
+ final Rect bottomHitRegion = new Rect();
- mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds));
- mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds));
+ // If we have existing split regions use those bounds, otherwise split it 50/50
+ if (inSplitScreen) {
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ topHitRegion.set(displayRegion);
+ topHitRegion.bottom = (int) centerX;
+ bottomHitRegion.set(displayRegion);
+ bottomHitRegion.top = (int) centerX;
+ } else {
+ displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
+ }
+
+ mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds, -1));
+ mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds,
+ -1));
+ }
}
} else {
// Split-screen not allowed, so only show the fullscreen target
- mTargets.add(new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
+ mTargets.add(new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion, -1));
}
return mTargets;
}
@@ -230,6 +330,22 @@
}
for (int i = mTargets.size() - 1; i >= 0; i--) {
SplitDragPolicy.Target t = mTargets.get(i);
+ if (enableFlexibleSplit() && mCurrentHoverTarget != null) {
+ // If we're in flexible split, the targets themselves animate, so we have to rely
+ // on the view's animated position for subsequent drag coordinates which we also
+ // cache in HoverAnimProps.
+ List<List<HoverAnimProps>> hoverAnimPropTargets =
+ mHoverAnimProps.get(mCurrentSnapPosition);
+ for (HoverAnimProps animProps :
+ hoverAnimPropTargets.get(mCurrentHoverTarget.index)) {
+ if (animProps.getHoverRect() != null &&
+ animProps.getHoverRect().contains(x, y)) {
+ return animProps.getTarget();
+ }
+ }
+
+ }
+
if (t.hitRegion.contains(x, y)) {
return t;
}
@@ -266,6 +382,10 @@
} else {
launchIntent(mSession, starter, position, hideTaskToken);
}
+
+ if (enableFlexibleSplit()) {
+ reset();
+ }
}
/**
@@ -335,6 +455,82 @@
null /* fillIntent */, position, opts, hideTaskToken);
}
+ @Override
+ public void onHoveringOver(Target hoverTarget) {
+ final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
+ final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
+ if (!inSplitScreen) {
+ // no need to animate for entering 50/50 split
+ return;
+ }
+
+ mCurrentHoverTarget = hoverTarget;
+ if (hoverTarget == null) {
+ // Reset to default state
+ BiConsumer<Target, View> biConsumer = new BiConsumer<Target, View>() {
+ @Override
+ public void accept(Target target, View view) {
+ // take into account left/right split
+ Animator transX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ target.drawRegion.left);
+ Animator transY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
+ target.drawRegion.top);
+ Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, 1);
+ Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, 1);
+ AnimatorSet as = new AnimatorSet();
+ as.play(transX);
+ as.play(transY);
+ as.play(scaleX);
+ as.play(scaleY);
+
+ as.start();
+ }
+ };
+ mDragZoneAnimator.animateDragTargets(List.of(biConsumer));
+ return;
+ }
+
+ // TODO(b/349828130) get this from split controller
+ @SplitScreenConstants.SnapPosition int snapPosition = SNAP_TO_2_50_50;
+ mCurrentSnapPosition = SNAP_TO_2_50_50;
+ List<BiConsumer<Target, View>> animatingConsumers = new ArrayList<>();
+ final List<List<HoverAnimProps>> hoverAnimProps = mHoverAnimProps.get(snapPosition);
+ List<HoverAnimProps> animProps = hoverAnimProps.get(hoverTarget.index);
+ // Expand start and push out the rest to the end
+ BiConsumer<Target, View> biConsumer = new BiConsumer<>() {
+ @Override
+ public void accept(Target target, View view) {
+ if (animProps.isEmpty() || animProps.size() < (target.index + 1)) {
+ return;
+ }
+ HoverAnimProps singleAnimProp = animProps.get(target.index);
+ Animator transX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ singleAnimProp.getTransX());
+ Animator transY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
+ singleAnimProp.getTransY());
+ Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X,
+ singleAnimProp.getScaleX());
+ Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y,
+ singleAnimProp.getScaleY());
+ AnimatorSet as = new AnimatorSet();
+ as.play(transX);
+ as.play(transY);
+ as.play(scaleX);
+ as.play(scaleY);
+ as.start();
+ }
+ };
+ animatingConsumers.add(biConsumer);
+ mDragZoneAnimator.animateDragTargets(animatingConsumers);
+ }
+
+ private void reset() {
+ mCurrentHoverTarget = null;
+ mCurrentSnapPosition = -1;
+ }
+
+
+
/**
* Interface for actually committing the task launches.
*/
@@ -425,7 +621,7 @@
*/
public static class Target {
static final int TYPE_FULLSCREEN = 0;
- static final int TYPE_SPLIT_LEFT = 1;
+ public static final int TYPE_SPLIT_LEFT = 1;
static final int TYPE_SPLIT_TOP = 2;
static final int TYPE_SPLIT_RIGHT = 3;
static final int TYPE_SPLIT_BOTTOM = 4;
@@ -445,16 +641,23 @@
final Rect hitRegion;
// The approximate visual region for where the task will start
final Rect drawRegion;
+ int index;
- public Target(@Type int t, Rect hit, Rect draw) {
+ /**
+ * @param index 0-indexed, represents which position of drop target this object represents,
+ * 0 to N for left to right, top to bottom
+ */
+ public Target(@Type int t, Rect hit, Rect draw, int index) {
type = t;
hitRegion = hit;
drawRegion = draw;
+ this.index = index;
}
@Override
public String toString() {
- return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion + "}";
+ return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion
+ + " index=" + index + "}";
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/DropTargetAnimSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/DropTargetAnimSupplier.kt
new file mode 100644
index 0000000..bb34613
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/DropTargetAnimSupplier.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop.anim
+
+import android.content.res.Resources
+import android.graphics.Insets
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.draganddrop.SplitDragPolicy
+
+/**
+ * When the user is dragging an icon from Taskbar to add an app into split
+ * screen, we have a set of rules by which we draw and move colored drop
+ * targets around the screen. The rules are provided through this interface.
+ *
+ * Each possible screen layout should have an implementation of this interface.
+ * E.g.
+ * - 50:50 two-app split
+ * - 10:45:45 three-app split
+ * - single app, no split
+ * = three implementations of this interface.
+ */
+interface DropTargetAnimSupplier {
+ /**
+ * Returns a Pair of lists.
+ * First list (length n): Where to draw the n colored drop zones.
+ * Second list (length n): How to animate the drop zones as user hovers around.
+ *
+ * Ex: First list => [A, B, C] // 3 views will be created representing these 3 targets
+ * Second list => [
+ * [A (scaleX=4), B (translateX=20), C (translateX=20)], // hovering over A
+ * [A (translateX=20), B (scaleX=4), C (translateX=20)], // hovering over B
+ * [A (translateX=20), B (translateX=20), C (scaleX=4)], // hovering over C
+ * ]
+ *
+ * All indexes assume 0 to N => left to right when [isLeftRightSplit] is true and top to bottom
+ * when [isLeftRightSplit] is false. Indexing is left to right even in RtL mode.
+ *
+ * All lists should have the SAME number of elements, even if no animations are to be run for
+ * a given target while in a hover state.
+ * It's not that we don't trust you, but we _really_ don't trust you, so this will throw an
+ * exception if lengths are different. Don't ruin it for everyone else...
+ * or do. Idk, you're an adult.
+ */
+ fun getTargets(displayLayout: DisplayLayout, insets: Insets, isLeftRightSplit: Boolean,
+ resources: Resources) :
+ Pair<List<SplitDragPolicy.Target>, List<List<HoverAnimProps>>>
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/HoverAnimProps.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/HoverAnimProps.kt
new file mode 100644
index 0000000..d61caeb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/HoverAnimProps.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop.anim
+
+import android.graphics.Rect
+import com.android.wm.shell.draganddrop.SplitDragPolicy
+
+/**
+ * Contains the animation props to represent a single state of drop targets.
+ * When the user is dragging, we'd be going between different HoverAnimProps
+ */
+data class HoverAnimProps(
+ var target: SplitDragPolicy.Target,
+ val transX: Float,
+ val transY: Float,
+ val scaleX: Float,
+ val scaleY: Float,
+ /**
+ * Pass in null to indicate this target cannot be hovered over for this given animation/
+ * state
+ *
+ * TODO: There's some way we can probably use the existing translation/scaling values
+ * to take [.target]'s hitRect and scale that so we don't have to take in a separate
+ * hoverRect in the CTOR. Have to make sure the pivots match since view's pivot in the
+ * center of the view and rect's pivot at 0, 0 if unspecified.
+ * The two may also not be correlated, but worth investigating
+ *
+ */
+ var hoverRect: Rect?
+) {
+
+ override fun toString(): String {
+ return ("targetId: " + target
+ + " translationX: " + transX
+ + " translationY: " + transY
+ + " scaleX: " + scaleX
+ + " scaleY: " + scaleY
+ + " hoverRect: " + hoverRect)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt
new file mode 100644
index 0000000..9f532f5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop.anim
+
+import android.content.res.Resources
+import android.graphics.Insets
+import android.graphics.Rect
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.draganddrop.SplitDragPolicy.Target
+
+/**
+ * Represents Drop Zone targets and animations for when the system is currently in a 2 app 50/50
+ * split.
+ * SnapPosition = 2_50_50
+ *
+ * NOTE: Naming convention for many variables is done as "hXtYZ"
+ * This means that variable is a transformation on the Z property for target index Y while the user
+ * is hovering over target index X
+ * Ex: h1t2scaleX=2 => User is hovering over target index 1, target index 2 should scaleX by 2
+ *
+ * TODO(b/349828130): Everything in this class is temporary, none of this is up to spec.
+ */
+class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier {
+ /**
+ * TODO: Could we transpose all the horizontal rects by 90 degrees and have that suffice for
+ * top bottom split?? Hmmm... Doubt it.
+ */
+ override fun getTargets(
+ displayLayout: DisplayLayout,
+ insets: Insets,
+ isLeftRightSplit: Boolean,
+ resources: Resources
+ ): Pair<List<Target>, List<List<HoverAnimProps>>> {
+ val targets : ArrayList<Target> = ArrayList()
+ val w: Int = displayLayout.width()
+ val h: Int = displayLayout.height()
+ val iw = w - insets.left - insets.right
+ val ih = h - insets.top - insets.bottom
+ val l = insets.left
+ val t = insets.top
+ val displayRegion = Rect(l, t, l + iw, t + ih)
+ val fullscreenDrawRegion = Rect(displayRegion)
+ val dividerWidth: Float = resources.getDimensionPixelSize(
+ R.dimen.split_divider_bar_width
+ ).toFloat()
+
+ val farStartBounds = Rect()
+ farStartBounds.set(displayRegion)
+ val startBounds = Rect()
+ startBounds.set(displayRegion)
+ val endBounds = Rect()
+ endBounds.set(displayRegion)
+ val farEndBounds = Rect()
+ farEndBounds.set(displayRegion)
+ val endsPercent = 0.10f
+ val visibleStagePercent = 0.45f
+ val halfDividerWidth = dividerWidth.toInt() / 2
+ val endsWidth = Math.round(displayRegion.width() * endsPercent)
+ val stageWidth = Math.round(displayRegion.width() * visibleStagePercent)
+
+
+ // Place the farStart and farEnds outside of the display, and then
+ // animate them in once the hover starts
+ // | = divider; || = display boundary
+ // farStart || start | end || farEnd
+ farStartBounds.left = -endsWidth
+ farStartBounds.right = 0
+ startBounds.left = farStartBounds.right + dividerWidth.toInt()
+ startBounds.right = startBounds.left + stageWidth
+ endBounds.left = startBounds.right + dividerWidth.toInt()
+ endBounds.right = endBounds.left + stageWidth
+ farEndBounds.left = fullscreenDrawRegion.right
+ farEndBounds.right = farEndBounds.left + endsWidth
+
+
+ // For the hit rect, trim the divider space we've added between the
+ // rects
+ targets.add(
+ Target(
+ Target.TYPE_SPLIT_LEFT,
+ Rect(
+ farStartBounds.left, farStartBounds.top,
+ farStartBounds.right + halfDividerWidth,
+ farStartBounds.bottom
+ ),
+ farStartBounds, 0
+ )
+ )
+ targets.add(
+ Target(
+ Target.TYPE_SPLIT_LEFT,
+ Rect(
+ startBounds.left - halfDividerWidth,
+ startBounds.top,
+ startBounds.right + halfDividerWidth,
+ startBounds.bottom
+ ),
+ startBounds, 1
+ )
+ )
+ targets.add(
+ Target(
+ Target.TYPE_SPLIT_LEFT,
+ Rect(
+ endBounds.left - halfDividerWidth,
+ endBounds.top, endBounds.right, endBounds.bottom
+ ),
+ endBounds, 2
+ )
+ )
+ targets.add(
+ Target(
+ Target.TYPE_SPLIT_LEFT,
+ Rect(
+ farEndBounds.left - halfDividerWidth,
+ farEndBounds.top, farEndBounds.right, farEndBounds.bottom
+ ),
+ farEndBounds, 3
+ )
+ )
+
+
+ // Hovering over target 0,
+ // * increase scaleX of target 0
+ // * decrease scaleX of target 1, 2
+ // * ensure target 3 offscreen
+
+ // bring target 0 in from offscreen and expand
+ val h0t0ScaleX = stageWidth.toFloat() / endsWidth
+ val h0t0TransX: Float = stageWidth / h0t0ScaleX + dividerWidth
+ val h0t0HoverProps = HoverAnimProps(
+ targets.get(0),
+ h0t0TransX, farStartBounds.top.toFloat(), h0t0ScaleX, 1f,
+ Rect(
+ 0, 0, (stageWidth + dividerWidth).toInt(),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 1 over to the middle/end
+ val h0t1TransX = stageWidth.toFloat()
+ val h0t1ScaleX = 1f
+ val h0t1HoverProps = HoverAnimProps(
+ targets.get(1),
+ h0t1TransX, startBounds.top.toFloat(), h0t1ScaleX, 1f,
+ Rect(
+ stageWidth, 0, (stageWidth + h0t1TransX).toInt(),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 2 to the very end
+ val h0t2TransX = endBounds.left + stageWidth / 2f
+ val h0t2ScaleX = endsWidth.toFloat() / stageWidth
+ val h0t2HoverProps = HoverAnimProps(
+ targets.get(2),
+ h0t2TransX, endBounds.top.toFloat(), h0t2ScaleX, 1f,
+ Rect(
+ displayRegion.right as Int - endsWidth, 0,
+ displayRegion.right as Int,
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 3 off-screen
+ val h0t3TransX = farEndBounds.right.toFloat()
+ val h0t3ScaleX = 1f
+ val h0t3HoverProps = HoverAnimProps(
+ targets.get(3),
+ h0t3TransX, farEndBounds.top.toFloat(), h0t3ScaleX, 1f,
+ null
+ )
+ val animPropsForHoverTarget0 =
+ listOf(h0t0HoverProps, h0t1HoverProps, h0t2HoverProps, h0t3HoverProps)
+
+
+ // Hovering over target 1,
+ // * Bring in target 0 from offscreen start
+ // * Shift over target 1
+ // * Slightly lower scale of target 2
+ // * Ensure target 4 offscreen
+ // bring target 0 in from offscreen
+ val h1t0TransX = 0f
+ val h1t0ScaleX = 1f
+ val h1t0HoverProps = HoverAnimProps(
+ targets.get(0),
+ h1t0TransX, farStartBounds.top.toFloat(), h1t0ScaleX, 1f,
+ Rect(
+ 0, 0, (farStartBounds.width() + dividerWidth).toInt(),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 1 over a tiny bit by same amount and make it smaller
+ val h1t1TransX: Float = endsWidth + dividerWidth
+ val h1t1ScaleX = 1f
+ val h1t1HoverProps = HoverAnimProps(
+ targets.get(1),
+ h1t1TransX, startBounds.top.toFloat(), h1t1ScaleX, 1f,
+ Rect(
+ h1t1TransX.toInt(), 0, (h1t1TransX + stageWidth).toInt(),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 2 to the very end
+ val h1t2TransX = (endBounds.left + farStartBounds.width()).toFloat()
+ val h1t2ScaleX = h1t1ScaleX
+ val h1t2HoverProps = HoverAnimProps(
+ targets.get(2),
+ h1t2TransX, endBounds.top.toFloat(), h1t2ScaleX, 1f,
+ Rect(
+ endBounds.left + farStartBounds.width(),
+ 0,
+ (endBounds.left + farStartBounds.width() + stageWidth),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 3 off-screen, default laid out is off-screen
+ val h1t3TransX = farEndBounds.right.toFloat()
+ val h1t3ScaleX = 1f
+ val h1t3HoverProps = HoverAnimProps(
+ targets.get(3),
+ h1t3TransX, farEndBounds.top.toFloat(), h1t3ScaleX, 1f,
+ null
+ )
+ val animPropsForHoverTarget1 =
+ listOf(h1t0HoverProps, h1t1HoverProps, h1t2HoverProps, h1t3HoverProps)
+
+
+ // Hovering over target 2,
+ // * Ensure Target 0 offscreen
+ // * Ensure target 1 back to start, slightly smaller scale
+ // * Slightly lower scale of target 2
+ // * Bring target 4 on screen
+ // reset target 0
+ val h2t0TransX = farStartBounds.left.toFloat()
+ val h2t0ScaleX = 1f
+ val h2t0HoverProps = HoverAnimProps(
+ targets.get(0),
+ h2t0TransX, farStartBounds.top.toFloat(), h2t0ScaleX, 1f,
+ null
+ )
+
+
+ // move target 1 over a tiny bit by same amount and make it smaller
+ val h2t1TransX = startBounds.left.toFloat()
+ val h2t1ScaleX = 1f
+ val h2t1HoverProps = HoverAnimProps(
+ targets.get(1),
+ h2t1TransX, startBounds.top.toFloat(), h2t1ScaleX, 1f,
+ Rect(
+ startBounds.left, 0,
+ (startBounds.left + stageWidth),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 2 to the very end
+ val h2t2TransX = endBounds.left.toFloat()
+ val h2t2ScaleX = h2t1ScaleX
+ val h2t2HoverProps = HoverAnimProps(
+ targets.get(2),
+ h2t2TransX, endBounds.top.toFloat(), h2t2ScaleX, 1f,
+ Rect(
+ (startBounds.right + dividerWidth).toInt(),
+ 0,
+ endBounds.left + stageWidth,
+ farStartBounds.bottom
+ )
+ )
+
+
+ // bring target 3 on-screen
+ val h2t3TransX = (farEndBounds.left - farEndBounds.width()).toFloat()
+ val h2t3ScaleX = 1f
+ val h2t3HoverProps = HoverAnimProps(
+ targets.get(3),
+ h2t3TransX, farEndBounds.top.toFloat(), h2t3ScaleX, 1f,
+ Rect(
+ endBounds.right,
+ 0,
+ displayRegion.right,
+ farStartBounds.bottom
+ )
+ )
+ val animPropsForHoverTarget2 =
+ listOf(h2t0HoverProps, h2t1HoverProps, h2t2HoverProps, h2t3HoverProps)
+
+
+ // Hovering over target 3,
+ // * Ensure Target 0 offscreen
+ // * Ensure target 1 back to start, slightly smaller scale
+ // * Slightly lower scale of target 2
+ // * Bring target 4 on screen and scale up
+ // reset target 0
+ val h3t0TransX = farStartBounds.left.toFloat()
+ val h3t0ScaleX = 1f
+ val h3t0HoverProps = HoverAnimProps(
+ targets.get(0),
+ h3t0TransX, farStartBounds.top.toFloat(), h3t0ScaleX, 1f,
+ null
+ )
+
+
+ // move target 1 over a tiny bit by same amount and make it smaller
+ val h3t1ScaleX = endsWidth.toFloat() / stageWidth
+ val h3t1TransX = 0 - (stageWidth / (1 / h3t1ScaleX))
+ val h3t1HoverProps = HoverAnimProps(
+ targets.get(1),
+ h3t1TransX, startBounds.top.toFloat(), h3t1ScaleX, 1f,
+ Rect(
+ 0, 0,
+ endsWidth,
+ farStartBounds.bottom
+ )
+ )
+
+
+ // move target 2 towards the start
+ val h3t2TransX: Float = endsWidth + dividerWidth
+ val h3t2ScaleX = 1f
+ val h3t2HoverProps = HoverAnimProps(
+ targets.get(2),
+ h3t2TransX, endBounds.top.toFloat(), h3t2ScaleX, 1f,
+ Rect(
+ endsWidth, 0,
+ (endsWidth + stageWidth + dividerWidth).toInt(),
+ farStartBounds.bottom
+ )
+ )
+
+
+ // bring target 3 on-screen and expand
+ val h3t3ScaleX = stageWidth.toFloat() / endsWidth
+ val h3t3TransX = endBounds.right - stageWidth / 2f
+ val h3t3HoverProps = HoverAnimProps(
+ targets.get(3),
+ h3t3TransX, farEndBounds.top.toFloat(), h3t3ScaleX, 1f,
+ Rect(
+ displayRegion.right - stageWidth, 0,
+ displayRegion.right,
+ farStartBounds.bottom
+ )
+ )
+ val animPropsForHoverTarget3 =
+ listOf(h3t0HoverProps, h3t1HoverProps, h3t2HoverProps, h3t3HoverProps)
+
+ return Pair(targets, listOf(animPropsForHoverTarget0, animPropsForHoverTarget1,
+ animPropsForHoverTarget2, animPropsForHoverTarget3))
+
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 87b661d..e77467d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -322,6 +322,22 @@
return mTaskOrganizer.getRunningTaskInfo(taskId);
}
+ /**
+ * @return an Array of RunningTaskInfo's ordered by leftToRight or topTopBottom
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo[] getAllTaskInfos() {
+ // TODO(b/349828130) Add the third stage task info and not rely on positions
+ ActivityManager.RunningTaskInfo topLeftTask = getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ ActivityManager.RunningTaskInfo bottomRightTask =
+ getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ if (topLeftTask != null && bottomRightTask != null) {
+ return new ActivityManager.RunningTaskInfo[]{topLeftTask, bottomRightTask};
+ }
+
+ return null;
+ }
+
/** Check task is under split or not by taskId. */
public boolean isTaskInSplitScreen(int taskId) {
return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
index 46b60499..eb74218 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
@@ -150,7 +150,8 @@
mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
mInsets = Insets.of(0, 0, 0, 0);
- mPolicy = spy(new SplitDragPolicy(mContext, mSplitScreenStarter, mFullscreenStarter));
+ mPolicy = spy(new SplitDragPolicy(mContext, mSplitScreenStarter, mFullscreenStarter,
+ mock(DragZoneAnimator.class)));
mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
mLaunchableIntentPendingIntent = mock(PendingIntent.class);
when(mLaunchableIntentPendingIntent.getCreatorUserHandle())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt
new file mode 100644
index 0000000..4a53a7a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.AutomaticZenRule
+import android.app.NotificationManager
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ZenModesCleanupStartableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ @Mock private lateinit var notificationManager: NotificationManager
+
+ private lateinit var underTest: ZenModesCleanupStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ ZenModesCleanupStartable(
+ testScope.backgroundScope,
+ kosmos.backgroundCoroutineContext,
+ notificationManager,
+ )
+ }
+
+ @Test
+ fun start_withGamingModeZenRule_deletesIt() =
+ testScope.runTest {
+ whenever(notificationManager.automaticZenRules)
+ .thenReturn(
+ mutableMapOf(
+ Pair(
+ "gaming",
+ AutomaticZenRule.Builder(
+ "Gaming Mode",
+ Uri.parse(
+ "android-app://com.android.systemui/game-mode-dnd-controller"
+ ),
+ )
+ .setPackage("com.android.systemui")
+ .build(),
+ ),
+ Pair(
+ "other",
+ AutomaticZenRule.Builder("Other Mode", Uri.parse("something-else"))
+ .setPackage("com.other.package")
+ .build(),
+ ),
+ )
+ )
+
+ underTest.start()
+ runCurrent()
+
+ verify(notificationManager).removeAutomaticZenRule(eq("gaming"))
+ }
+
+ @Test
+ fun start_withoutGamingModeZenRule_doesNothing() =
+ testScope.runTest {
+ whenever(notificationManager.automaticZenRules)
+ .thenReturn(
+ mutableMapOf(
+ Pair(
+ "other",
+ AutomaticZenRule.Builder("Other Mode", Uri.parse("something-else"))
+ .setPackage("com.android.systemui")
+ .build(),
+ )
+ )
+ )
+
+ underTest.start()
+ runCurrent()
+
+ verify(notificationManager, never()).removeAutomaticZenRule(any())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index ca5f49d..684ce48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -88,6 +88,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.ZenModesCleanupStartable;
import dagger.Binds;
import dagger.Module;
@@ -299,4 +300,10 @@
ZenModeRepository repository) {
return new NotificationsSoundPolicyInteractor(repository);
}
+
+ /** Binds {@link ZenModesCleanupStartable} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(ZenModesCleanupStartable.class)
+ CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartable.kt
new file mode 100644
index 0000000..32b476b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartable.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.NotificationManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.modes.shared.ModesUi
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Cleanup task that deletes the obsolete "Gaming" AutomaticZenRule that was created by SystemUI in
+ * the faraway past, and still exists on some devices through upgrades or B&R.
+ */
+// TODO: b/372874878 - Remove this thing once it has run long enough
+class ZenModesCleanupStartable
+@Inject
+constructor(
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ @Background private val bgContext: CoroutineContext,
+ val notificationManager: NotificationManager,
+) : CoreStartable {
+
+ override fun start() {
+ if (!ModesUi.isEnabled) {
+ return
+ }
+ applicationCoroutineScope.launch { deleteObsoleteGamingMode() }
+ }
+
+ private suspend fun deleteObsoleteGamingMode() {
+ withContext(bgContext) {
+ val allRules = notificationManager.automaticZenRules
+ val gamingModeEntry =
+ allRules.entries.firstOrNull { entry ->
+ entry.value.packageName == "com.android.systemui" &&
+ entry.value.conditionId?.toString() ==
+ "android-app://com.android.systemui/game-mode-dnd-controller"
+ }
+ if (gamingModeEntry != null) {
+ notificationManager.removeAutomaticZenRule(gamingModeEntry.key)
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 5514ec7..dc2c957 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -62,7 +62,6 @@
import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.integrity.engine.RuleEvaluationEngine;
import com.android.server.integrity.model.IntegrityCheckResult;
@@ -214,12 +213,6 @@
version, ruleProvider));
}
- FrameworkStatsLog.write(
- FrameworkStatsLog.INTEGRITY_RULES_PUSHED,
- success,
- ruleProvider,
- version);
-
Intent intent = new Intent();
intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
try {
@@ -346,15 +339,6 @@
packageName, result.getEffect(), result.getMatchedRules()));
}
- FrameworkStatsLog.write(
- FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED,
- packageName,
- appCertificates.toString(),
- appInstallMetadata.getVersionCode(),
- installerPackageName,
- result.getLoggingResponse(),
- result.isCausedByAppCertRule(),
- result.isCausedByInstallerRule());
mPackageManagerInternal.setIntegrityVerificationResult(
verificationId,
result.getEffect() == IntegrityCheckResult.Effect.ALLOW
diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
index 1fa0670..b0647fc 100644
--- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
+++ b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.content.integrity.Rule;
-import com.android.internal.util.FrameworkStatsLog;
-
import java.util.Collections;
import java.util.List;
@@ -82,21 +80,6 @@
return new IntegrityCheckResult(Effect.DENY, ruleList);
}
- /**
- * Returns the in value of the integrity check result for logging purposes.
- */
- public int getLoggingResponse() {
- if (getEffect() == Effect.DENY) {
- return FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__REJECTED;
- } else if (getEffect() == Effect.ALLOW && getMatchedRules().isEmpty()) {
- return FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__ALLOWED;
- } else if (getEffect() == Effect.ALLOW && !getMatchedRules().isEmpty()) {
- return FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__FORCE_ALLOWED;
- } else {
- throw new IllegalStateException("IntegrityCheckResult is not valid.");
- }
- }
-
/** Returns true when the {@code mEffect} is caused by an app certificate mismatch. */
public boolean isCausedByAppCertRule() {
return mRuleList.stream().anyMatch(rule -> rule.getFormula().isAppCertificateFormula());
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 006a5bb..a235ba15 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -148,8 +148,8 @@
case HapticFeedbackConstants.SCROLL_TICK:
case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
case HapticFeedbackConstants.SCROLL_LIMIT:
- attrs = hapticFeedbackInputSourceCustomizationEnabled() ? TOUCH_VIBRATION_ATTRIBUTES
- : HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+ // TODO(b/372820923): use touch attributes by default.
+ attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
break;
case HapticFeedbackConstants.KEYBOARD_TAP:
case HapticFeedbackConstants.KEYBOARD_RELEASE:
@@ -176,14 +176,15 @@
int inputSource,
@HapticFeedbackConstants.Flags int flags,
@HapticFeedbackConstants.PrivateFlags int privFlags) {
- if (hapticFeedbackInputSourceCustomizationEnabled()
- && inputSource == InputDevice.SOURCE_ROTARY_ENCODER) {
+ if (hapticFeedbackInputSourceCustomizationEnabled()) {
switch (effectId) {
case HapticFeedbackConstants.SCROLL_TICK,
HapticFeedbackConstants.SCROLL_ITEM_FOCUS,
HapticFeedbackConstants.SCROLL_LIMIT -> {
- return getVibrationAttributesWithFlags(HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES,
- effectId, flags);
+ VibrationAttributes attrs = inputSource == InputDevice.SOURCE_ROTARY_ENCODER
+ ? HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES
+ : TOUCH_VIBRATION_ATTRIBUTES;
+ return getVibrationAttributesWithFlags(attrs, effectId, flags);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
index 6c23ff6..d31ed68 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java
@@ -22,8 +22,6 @@
import android.content.integrity.CompoundFormula;
import android.content.integrity.Rule;
-import com.android.internal.util.FrameworkStatsLog;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -40,8 +38,6 @@
assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW);
assertThat(allowResult.getMatchedRules()).isEmpty();
- assertThat(allowResult.getLoggingResponse())
- .isEqualTo(FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__ALLOWED);
}
@Test
@@ -58,9 +54,6 @@
assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW);
assertThat(allowResult.getMatchedRules()).containsExactly(forceAllowRule);
- assertThat(allowResult.getLoggingResponse())
- .isEqualTo(
- FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__FORCE_ALLOWED);
}
@Test
@@ -77,8 +70,6 @@
assertThat(denyResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.DENY);
assertThat(denyResult.getMatchedRules()).containsExactly(failedRule);
- assertThat(denyResult.getLoggingResponse())
- .isEqualTo(FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__REJECTED);
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index f7127df..3b2f532 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -453,20 +453,7 @@
}
@Test
- public void testVibrationAttribute_scrollFeedback_inputCustomizedFlag_useTouchUsage() {
- mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
- HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
-
- for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = provider.getVibrationAttributes(effectId, /* flags */
- 0, /* privFlags */ 0);
- assertWithMessage("Expected USAGE_TOUCH for scroll effect " + effectId
- + ", if no input customization").that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
- }
- }
-
- @Test
- public void testVibrationAttribute_scrollFeedback_noInputCustomizedFlag_useHardwareFeedback() {
+ public void testVibrationAttribute_scrollFeedback_useHardwareFeedback() {
HapticFeedbackVibrationProvider provider = createProviderWithoutCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {