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