1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.window;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
20 import static android.view.WindowManager.TransitionType;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.ActivityManager;
26 import android.app.WindowConfiguration;
27 import android.content.ComponentName;
28 import android.os.IBinder;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.view.WindowManager;
32 
33 /**
34  * A parcelable filter that can be used for rerouting transitions to a remote. This is a local
35  * representation so that the transition system doesn't need to make blocking queries over
36  * binder.
37  *
38  * @hide
39  */
40 public final class TransitionFilter implements Parcelable {
41 
42     /** The associated requirement doesn't care about the z-order. */
43     public static final int CONTAINER_ORDER_ANY = 0;
44     /** The associated requirement only matches the top-most (z-order) container. */
45     public static final int CONTAINER_ORDER_TOP = 1;
46 
47     /** @hide */
48     @IntDef(prefix = { "CONTAINER_ORDER_" }, value = {
49             CONTAINER_ORDER_ANY,
50             CONTAINER_ORDER_TOP,
51     })
52     public @interface ContainerOrder {}
53 
54     /**
55      * When non-null: this is a list of transition types that this filter applies to. This filter
56      * will fail for transitions that aren't one of these types.
57      */
58     @Nullable public @TransitionType int[] mTypeSet = null;
59 
60     /** All flags must be set on a transition. */
61     public @WindowManager.TransitionFlags int mFlags = 0;
62 
63     /** All flags must NOT be set on a transition. */
64     public @WindowManager.TransitionFlags int mNotFlags = 0;
65 
66     /**
67      * A list of required changes. To pass, a transition must meet all requirements.
68      */
69     @Nullable public Requirement[] mRequirements = null;
70 
TransitionFilter()71     public TransitionFilter() {
72     }
73 
TransitionFilter(Parcel in)74     private TransitionFilter(Parcel in) {
75         mTypeSet = in.createIntArray();
76         mFlags = in.readInt();
77         mNotFlags = in.readInt();
78         mRequirements = in.createTypedArray(Requirement.CREATOR);
79     }
80 
81     /** @return true if `info` meets all the requirements to pass this filter. */
matches(@onNull TransitionInfo info)82     public boolean matches(@NonNull TransitionInfo info) {
83         if (mTypeSet != null) {
84             // non-null typeset, so make sure info is one of the types.
85             boolean typePass = false;
86             for (int i = 0; i < mTypeSet.length; ++i) {
87                 if (info.getType() == mTypeSet[i]) {
88                     typePass = true;
89                     break;
90                 }
91             }
92             if (!typePass) return false;
93         }
94         if ((info.getFlags() & mFlags) != mFlags) {
95             return false;
96         }
97         if ((info.getFlags() & mNotFlags) != 0) {
98             return false;
99         }
100         // Make sure info meets all of the requirements.
101         if (mRequirements != null) {
102             for (int i = 0; i < mRequirements.length; ++i) {
103                 final boolean matches = mRequirements[i].matches(info);
104                 if (matches == mRequirements[i].mNot) {
105                     return false;
106                 }
107             }
108         }
109         return true;
110     }
111 
112     @Override
113     /** @hide */
writeToParcel(@onNull Parcel dest, int flags)114     public void writeToParcel(@NonNull Parcel dest, int flags) {
115         dest.writeIntArray(mTypeSet);
116         dest.writeInt(mFlags);
117         dest.writeInt(mNotFlags);
118         dest.writeTypedArray(mRequirements, flags);
119     }
120 
121     @NonNull
122     public static final Creator<TransitionFilter> CREATOR =
123             new Creator<TransitionFilter>() {
124                 @Override
125                 public TransitionFilter createFromParcel(Parcel in) {
126                     return new TransitionFilter(in);
127                 }
128 
129                 @Override
130                 public TransitionFilter[] newArray(int size) {
131                     return new TransitionFilter[size];
132                 }
133             };
134 
135     @Override
136     /** @hide */
describeContents()137     public int describeContents() {
138         return 0;
139     }
140 
141     @Override
toString()142     public String toString() {
143         StringBuilder sb = new StringBuilder();
144         sb.append("{types=[");
145         if (mTypeSet != null) {
146             for (int i = 0; i < mTypeSet.length; ++i) {
147                 sb.append((i == 0 ? "" : ",") + WindowManager.transitTypeToString(mTypeSet[i]));
148             }
149         }
150         sb.append("] flags=0x" + Integer.toHexString(mFlags));
151         sb.append("] notFlags=0x" + Integer.toHexString(mNotFlags));
152         sb.append(" checks=[");
153         if (mRequirements != null) {
154             for (int i = 0; i < mRequirements.length; ++i) {
155                 sb.append((i == 0 ? "" : ",") + mRequirements[i]);
156             }
157         }
158         return sb.append("]}").toString();
159     }
160 
161     /**
162      * Matches a change that a transition must contain to pass this filter. All requirements in a
163      * filter must be met to pass the filter.
164      */
165     public static final class Requirement implements Parcelable {
166         public int mActivityType = ACTIVITY_TYPE_UNDEFINED;
167 
168         /** This only matches if the change is independent of its parents. */
169         public boolean mMustBeIndependent = true;
170 
171         /** If this matches, the parent filter will fail */
172         public boolean mNot = false;
173 
174         public int[] mModes = null;
175 
176         /** Matches only if all the flags here are set on the change. */
177         public @TransitionInfo.ChangeFlags int mFlags = 0;
178 
179         /** If this needs to be a task. */
180         public boolean mMustBeTask = false;
181 
182         public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
183         public ComponentName mTopActivity;
184         public IBinder mLaunchCookie;
185 
Requirement()186         public Requirement() {
187         }
188 
Requirement(Parcel in)189         private Requirement(Parcel in) {
190             mActivityType = in.readInt();
191             mMustBeIndependent = in.readBoolean();
192             mNot = in.readBoolean();
193             mModes = in.createIntArray();
194             mFlags = in.readInt();
195             mMustBeTask = in.readBoolean();
196             mOrder = in.readInt();
197             mTopActivity = in.readTypedObject(ComponentName.CREATOR);
198             mLaunchCookie = in.readStrongBinder();
199         }
200 
201         /** Go through changes and find if at-least one change matches this filter */
matches(@onNull TransitionInfo info)202         boolean matches(@NonNull TransitionInfo info) {
203             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
204                 final TransitionInfo.Change change = info.getChanges().get(i);
205                 if (mMustBeIndependent && !TransitionInfo.isIndependent(change, info)) {
206                     // Only look at independent animating windows.
207                     continue;
208                 }
209                 if (mOrder == CONTAINER_ORDER_TOP && i > 0) {
210                     continue;
211                 }
212                 if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
213                     if (change.getTaskInfo() == null
214                             || change.getTaskInfo().getActivityType() != mActivityType) {
215                         continue;
216                     }
217                 }
218                 if (!matchesTopActivity(change.getTaskInfo(), change.getActivityComponent())) {
219                     continue;
220                 }
221                 if (mModes != null) {
222                     boolean pass = false;
223                     for (int m = 0; m < mModes.length; ++m) {
224                         if (mModes[m] == change.getMode()) {
225                             pass = true;
226                             break;
227                         }
228                     }
229                     if (!pass) continue;
230                 }
231                 if ((change.getFlags() & mFlags) != mFlags) {
232                     continue;
233                 }
234                 if (mMustBeTask && change.getTaskInfo() == null) {
235                     continue;
236                 }
237                 if (!matchesCookie(change.getTaskInfo())) {
238                     continue;
239                 }
240                 return true;
241             }
242             return false;
243         }
244 
matchesTopActivity(ActivityManager.RunningTaskInfo taskInfo, @Nullable ComponentName activityComponent)245         private boolean matchesTopActivity(ActivityManager.RunningTaskInfo taskInfo,
246                 @Nullable ComponentName activityComponent) {
247             if (mTopActivity == null) return true;
248             if (activityComponent != null) {
249                 return mTopActivity.equals(activityComponent);
250             } else if (taskInfo != null) {
251                 return mTopActivity.equals(taskInfo.topActivity);
252             }
253             return false;
254         }
255 
matchesCookie(ActivityManager.RunningTaskInfo info)256         private boolean matchesCookie(ActivityManager.RunningTaskInfo info) {
257             if (mLaunchCookie == null) return true;
258             if (info == null) return false;
259             for (IBinder cookie : info.launchCookies) {
260                 if (mLaunchCookie.equals(cookie)) {
261                     return true;
262                 }
263             }
264             return false;
265         }
266 
267         /** Check if the request matches this filter. It may generate false positives */
matches(@onNull TransitionRequestInfo request)268         boolean matches(@NonNull TransitionRequestInfo request) {
269             // Can't check modes/order since the transition hasn't been built at this point.
270             if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
271             return request.getTriggerTask() != null
272                     && request.getTriggerTask().getActivityType() == mActivityType
273                     && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */)
274                     && matchesCookie(request.getTriggerTask());
275         }
276 
277         @Override
278         /** @hide */
writeToParcel(@onNull Parcel dest, int flags)279         public void writeToParcel(@NonNull Parcel dest, int flags) {
280             dest.writeInt(mActivityType);
281             dest.writeBoolean(mMustBeIndependent);
282             dest.writeBoolean(mNot);
283             dest.writeIntArray(mModes);
284             dest.writeInt(mFlags);
285             dest.writeBoolean(mMustBeTask);
286             dest.writeInt(mOrder);
287             dest.writeTypedObject(mTopActivity, flags);
288             dest.writeStrongBinder(mLaunchCookie);
289         }
290 
291         @NonNull
292         public static final Creator<Requirement> CREATOR =
293                 new Creator<Requirement>() {
294                     @Override
295                     public Requirement createFromParcel(Parcel in) {
296                         return new Requirement(in);
297                     }
298 
299                     @Override
300                     public Requirement[] newArray(int size) {
301                         return new Requirement[size];
302                     }
303                 };
304 
305         @Override
306         /** @hide */
describeContents()307         public int describeContents() {
308             return 0;
309         }
310 
311         @Override
toString()312         public String toString() {
313             StringBuilder out = new StringBuilder();
314             out.append('{');
315             if (mNot) out.append("NOT ");
316             out.append("atype=" + WindowConfiguration.activityTypeToString(mActivityType));
317             out.append(" independent=" + mMustBeIndependent);
318             out.append(" modes=[");
319             if (mModes != null) {
320                 for (int i = 0; i < mModes.length; ++i) {
321                     out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
322                 }
323             }
324             out.append("]");
325             out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
326             out.append(" mustBeTask=" + mMustBeTask);
327             out.append(" order=" + containerOrderToString(mOrder));
328             out.append(" topActivity=").append(mTopActivity);
329             out.append(" launchCookie=").append(mLaunchCookie);
330             out.append("}");
331             return out.toString();
332         }
333     }
334 
containerOrderToString(int order)335     private static String containerOrderToString(int order) {
336         switch (order) {
337             case CONTAINER_ORDER_ANY: return "ANY";
338             case CONTAINER_ORDER_TOP: return "TOP";
339         }
340         return "UNKNOWN(" + order + ")";
341     }
342 }
343