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