• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 import static android.view.WindowManager.TransitionType;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager;
27 import android.app.WindowConfiguration;
28 import android.content.ComponentName;
29 import android.os.IBinder;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.view.WindowManager;
33 
34 /**
35  * A parcelable filter that can be used for rerouting transitions to a remote. This is a local
36  * representation so that the transition system doesn't need to make blocking queries over
37  * binder.
38  *
39  * @hide
40  */
41 public final class TransitionFilter implements Parcelable {
42 
43     /** The associated requirement doesn't care about the z-order. */
44     public static final int CONTAINER_ORDER_ANY = 0;
45     /** The associated requirement only matches the top-most (z-order) container. */
46     public static final int CONTAINER_ORDER_TOP = 1;
47 
48     /** @hide */
49     @IntDef(prefix = { "CONTAINER_ORDER_" }, value = {
50             CONTAINER_ORDER_ANY,
51             CONTAINER_ORDER_TOP,
52     })
53     public @interface ContainerOrder {}
54 
55     /**
56      * When non-null: this is a list of transition types that this filter applies to. This filter
57      * will fail for transitions that aren't one of these types.
58      */
59     @Nullable public @TransitionType int[] mTypeSet = null;
60 
61     /** All flags must be set on a transition. */
62     public @WindowManager.TransitionFlags int mFlags = 0;
63 
64     /** All flags must NOT be set on a transition. */
65     public @WindowManager.TransitionFlags int mNotFlags = 0;
66 
67     /**
68      * A list of required changes. To pass, a transition must meet all requirements.
69      */
70     @Nullable public Requirement[] mRequirements = null;
71 
TransitionFilter()72     public TransitionFilter() {
73     }
74 
TransitionFilter(Parcel in)75     private TransitionFilter(Parcel in) {
76         mTypeSet = in.createIntArray();
77         mFlags = in.readInt();
78         mNotFlags = in.readInt();
79         mRequirements = in.createTypedArray(Requirement.CREATOR);
80     }
81 
82     /** @return true if `info` meets all the requirements to pass this filter. */
matches(@onNull TransitionInfo info)83     public boolean matches(@NonNull TransitionInfo info) {
84         if (mTypeSet != null) {
85             // non-null typeset, so make sure info is one of the types.
86             boolean typePass = false;
87             for (int i = 0; i < mTypeSet.length; ++i) {
88                 if (info.getType() == mTypeSet[i]) {
89                     typePass = true;
90                     break;
91                 }
92             }
93             if (!typePass) return false;
94         }
95         if ((info.getFlags() & mFlags) != mFlags) {
96             return false;
97         }
98         if ((info.getFlags() & mNotFlags) != 0) {
99             return false;
100         }
101         // Make sure info meets all of the requirements.
102         if (mRequirements != null) {
103             for (int i = 0; i < mRequirements.length; ++i) {
104                 final boolean matches = mRequirements[i].matches(info);
105                 if (matches == mRequirements[i].mNot) {
106                     return false;
107                 }
108             }
109         }
110         return true;
111     }
112 
113     @Override
114     /** @hide */
writeToParcel(@onNull Parcel dest, int flags)115     public void writeToParcel(@NonNull Parcel dest, int flags) {
116         dest.writeIntArray(mTypeSet);
117         dest.writeInt(mFlags);
118         dest.writeInt(mNotFlags);
119         dest.writeTypedArray(mRequirements, flags);
120     }
121 
122     @NonNull
123     public static final Creator<TransitionFilter> CREATOR =
124             new Creator<TransitionFilter>() {
125                 @Override
126                 public TransitionFilter createFromParcel(Parcel in) {
127                     return new TransitionFilter(in);
128                 }
129 
130                 @Override
131                 public TransitionFilter[] newArray(int size) {
132                     return new TransitionFilter[size];
133                 }
134             };
135 
136     @Override
137     /** @hide */
describeContents()138     public int describeContents() {
139         return 0;
140     }
141 
142     @Override
toString()143     public String toString() {
144         StringBuilder sb = new StringBuilder();
145         sb.append("{types=[");
146         if (mTypeSet != null) {
147             for (int i = 0; i < mTypeSet.length; ++i) {
148                 sb.append((i == 0 ? "" : ",") + WindowManager.transitTypeToString(mTypeSet[i]));
149             }
150         }
151         sb.append("] flags=0x" + Integer.toHexString(mFlags));
152         sb.append("] notFlags=0x" + Integer.toHexString(mNotFlags));
153         sb.append(" checks=[");
154         if (mRequirements != null) {
155             for (int i = 0; i < mRequirements.length; ++i) {
156                 sb.append((i == 0 ? "" : ",") + mRequirements[i]);
157             }
158         }
159         return sb.append("]}").toString();
160     }
161 
162     /**
163      * Matches a change that a transition must contain to pass this filter. All requirements in a
164      * filter must be met to pass the filter.
165      */
166     public static final class Requirement implements Parcelable {
167         public int mActivityType = ACTIVITY_TYPE_UNDEFINED;
168 
169         /** This only matches if the change is independent of its parents. */
170         public boolean mMustBeIndependent = true;
171 
172         /** If this matches, the parent filter will fail */
173         public boolean mNot = false;
174 
175         public int[] mModes = null;
176 
177         /** Matches only if all the flags here are set on the change. */
178         public @TransitionInfo.ChangeFlags int mFlags = 0;
179 
180         /** If this needs to be a task. */
181         public boolean mMustBeTask = false;
182 
183         public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
184         public ComponentName mTopActivity;
185         public IBinder mLaunchCookie;
186 
187         /** If non-null, requires the change to specifically have or not-have a custom animation. */
188         public Boolean mCustomAnimation = null;
189         public IBinder mTaskFragmentToken = null;
190 
191         public int mWindowingMode = WINDOWING_MODE_UNDEFINED;
192 
Requirement()193         public Requirement() {
194         }
195 
Requirement(Parcel in)196         private Requirement(Parcel in) {
197             mActivityType = in.readInt();
198             mMustBeIndependent = in.readBoolean();
199             mNot = in.readBoolean();
200             mModes = in.createIntArray();
201             mFlags = in.readInt();
202             mMustBeTask = in.readBoolean();
203             mOrder = in.readInt();
204             mTopActivity = in.readTypedObject(ComponentName.CREATOR);
205             mLaunchCookie = in.readStrongBinder();
206             // 0: null, 1: false, 2: true
207             final int customAnimRaw = in.readInt();
208             mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
209             mTaskFragmentToken = in.readStrongBinder();
210             mWindowingMode = in.readInt();
211         }
212 
213         /** Go through changes and find if at-least one change matches this filter */
matches(@onNull TransitionInfo info)214         boolean matches(@NonNull TransitionInfo info) {
215             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
216                 final TransitionInfo.Change change = info.getChanges().get(i);
217 
218                 if (mTaskFragmentToken != null
219                         && !mTaskFragmentToken.equals(change.getTaskFragmentToken())) {
220                     continue;
221                 }
222 
223                 if (mMustBeIndependent && !TransitionInfo.isIndependent(change, info)) {
224                     // Only look at independent animating windows.
225                     continue;
226                 }
227                 if (mOrder == CONTAINER_ORDER_TOP && i > 0) {
228                     continue;
229                 }
230                 if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
231                     if (change.getTaskInfo() == null
232                             || change.getTaskInfo().getActivityType() != mActivityType) {
233                         continue;
234                     }
235                 }
236                 if (!matchesTopActivity(change.getTaskInfo(), change.getActivityComponent())) {
237                     continue;
238                 }
239                 if (mModes != null) {
240                     boolean pass = false;
241                     for (int m = 0; m < mModes.length; ++m) {
242                         if (mModes[m] == change.getMode()) {
243                             pass = true;
244                             break;
245                         }
246                     }
247                     if (!pass) continue;
248                 }
249                 if ((change.getFlags() & mFlags) != mFlags) {
250                     continue;
251                 }
252                 if (mMustBeTask && change.getTaskInfo() == null) {
253                     continue;
254                 }
255                 if (!matchesCookie(change.getTaskInfo())) {
256                     continue;
257                 }
258                 if (mCustomAnimation != null
259                         // only applies to activity/task
260                         && (change.getTaskInfo() != null
261                                 || change.getActivityComponent() != null)) {
262                     final TransitionInfo.AnimationOptions opts = change.getAnimationOptions();
263                     if (opts != null) {
264                         boolean canActuallyOverride = change.getTaskInfo() == null
265                                 || opts.getOverrideTaskTransition();
266                         if (mCustomAnimation != canActuallyOverride) {
267                             continue;
268                         }
269                     } else if (mCustomAnimation) {
270                         continue;
271                     }
272                 }
273                 if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
274                     if (change.getTaskInfo() == null
275                             || change.getTaskInfo().getWindowingMode() != mWindowingMode) {
276                         continue;
277                     }
278                 }
279                 return true;
280             }
281             return false;
282         }
283 
matchesTopActivity(ActivityManager.RunningTaskInfo taskInfo, @Nullable ComponentName activityComponent)284         private boolean matchesTopActivity(ActivityManager.RunningTaskInfo taskInfo,
285                 @Nullable ComponentName activityComponent) {
286             if (mTopActivity == null) return true;
287             if (activityComponent != null) {
288                 return mTopActivity.equals(activityComponent);
289             } else if (taskInfo != null) {
290                 return mTopActivity.equals(taskInfo.topActivity);
291             }
292             return false;
293         }
294 
matchesCookie(ActivityManager.RunningTaskInfo info)295         private boolean matchesCookie(ActivityManager.RunningTaskInfo info) {
296             if (mLaunchCookie == null) return true;
297             if (info == null) return false;
298             for (IBinder cookie : info.launchCookies) {
299                 if (mLaunchCookie.equals(cookie)) {
300                     return true;
301                 }
302             }
303             return false;
304         }
305 
306         /** Check if the request matches this filter. It may generate false positives */
matches(@onNull TransitionRequestInfo request)307         boolean matches(@NonNull TransitionRequestInfo request) {
308             // Can't check modes/order since the transition hasn't been built at this point.
309             if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
310             return request.getTriggerTask() != null
311                     && request.getTriggerTask().getActivityType() == mActivityType
312                     && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */)
313                     && matchesCookie(request.getTriggerTask());
314         }
315 
316         @Override
317         /** @hide */
writeToParcel(@onNull Parcel dest, int flags)318         public void writeToParcel(@NonNull Parcel dest, int flags) {
319             dest.writeInt(mActivityType);
320             dest.writeBoolean(mMustBeIndependent);
321             dest.writeBoolean(mNot);
322             dest.writeIntArray(mModes);
323             dest.writeInt(mFlags);
324             dest.writeBoolean(mMustBeTask);
325             dest.writeInt(mOrder);
326             dest.writeTypedObject(mTopActivity, flags);
327             dest.writeStrongBinder(mLaunchCookie);
328             int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
329             dest.writeInt(customAnimRaw);
330             dest.writeStrongBinder(mTaskFragmentToken);
331             dest.writeInt(mWindowingMode);
332         }
333 
334         @NonNull
335         public static final Creator<Requirement> CREATOR =
336                 new Creator<Requirement>() {
337                     @Override
338                     public Requirement createFromParcel(Parcel in) {
339                         return new Requirement(in);
340                     }
341 
342                     @Override
343                     public Requirement[] newArray(int size) {
344                         return new Requirement[size];
345                     }
346                 };
347 
348         @Override
349         /** @hide */
describeContents()350         public int describeContents() {
351             return 0;
352         }
353 
354         @Override
toString()355         public String toString() {
356             StringBuilder out = new StringBuilder();
357             out.append('{');
358             if (mNot) out.append("NOT ");
359             out.append("atype=" + WindowConfiguration.activityTypeToString(mActivityType));
360             out.append(" independent=" + mMustBeIndependent);
361             out.append(" modes=[");
362             if (mModes != null) {
363                 for (int i = 0; i < mModes.length; ++i) {
364                     out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
365                 }
366             }
367             out.append("]");
368             out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
369             out.append(" mustBeTask=" + mMustBeTask);
370             out.append(" order=" + containerOrderToString(mOrder));
371             out.append(" topActivity=").append(mTopActivity);
372             out.append(" launchCookie=").append(mLaunchCookie);
373             if (mCustomAnimation != null) {
374                 out.append(" customAnim=").append(mCustomAnimation.booleanValue());
375             }
376             if (mTaskFragmentToken != null) {
377                 out.append(" taskFragmentToken=").append(mTaskFragmentToken);
378             }
379             out.append(" windowingMode="
380                     + WindowConfiguration.windowingModeToString(mWindowingMode));
381             out.append("}");
382             return out.toString();
383         }
384     }
385 
containerOrderToString(int order)386     private static String containerOrderToString(int order) {
387         switch (order) {
388             case CONTAINER_ORDER_ANY: return "ANY";
389             case CONTAINER_ORDER_TOP: return "TOP";
390         }
391         return "UNKNOWN(" + order + ")";
392     }
393 }
394