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