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