1 /* 2 * Copyright (C) 2017 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.android.apps.common.testing.accessibility.framework.replacements; 16 17 import static com.google.common.base.Preconditions.checkNotNull; 18 import static java.lang.Math.max; 19 import static java.lang.Math.min; 20 21 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AndroidFrameworkProtos.RectProto; 22 import com.google.errorprone.annotations.Immutable; 23 import java.util.regex.Matcher; 24 import java.util.regex.Pattern; 25 import org.checkerframework.checker.nullness.qual.Nullable; 26 27 /** Used as a local immutable replacement for Android's {@link android.graphics.Rect} */ 28 @Immutable 29 public final class Rect { 30 31 public static final Rect EMPTY = new Rect(0, 0, 0, 0); 32 33 private static final Pattern FLATTENED_PATTERN = 34 Pattern.compile("(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)"); 35 36 private final int left; 37 private final int top; 38 private final int right; 39 private final int bottom; 40 Rect(int left, int top, int right, int bottom)41 public Rect(int left, int top, int right, int bottom) { 42 // Ensure coordinate ordering is sensible 43 this.left = min(left, right); 44 this.top = min(top, bottom); 45 this.right = max(left, right); 46 this.bottom = max(top, bottom); 47 } 48 Rect(RectProto rectProto)49 public Rect(RectProto rectProto) { 50 this(rectProto.getLeft(), rectProto.getTop(), rectProto.getRight(), rectProto.getBottom()); 51 } 52 53 /** See {@link android.graphics.Rect#left} */ getLeft()54 public final int getLeft() { 55 return left; 56 } 57 58 /** See {@link android.graphics.Rect#top} */ getTop()59 public final int getTop() { 60 return top; 61 } 62 63 /** See {@link android.graphics.Rect#right} */ getRight()64 public final int getRight() { 65 return right; 66 } 67 68 /** See {@link android.graphics.Rect#bottom} */ getBottom()69 public final int getBottom() { 70 return bottom; 71 } 72 73 /** See {@link android.graphics.Rect#width()} */ getWidth()74 public final int getWidth() { 75 return right - left; 76 } 77 78 /** See {@link android.graphics.Rect#height()} */ getHeight()79 public final int getHeight() { 80 return bottom - top; 81 } 82 area()83 public final int area() { 84 return getWidth() * getHeight(); 85 } 86 87 /** See {@link android.graphics.Rect#contains(android.graphics.Rect)} */ contains(Rect r)88 public boolean contains(Rect r) { 89 return !isEmpty() 90 && (this.left <= r.left) 91 && (this.top <= r.top) 92 && (this.right >= r.right) 93 && (this.bottom >= r.bottom); 94 } 95 96 /** See {@link android.graphics.Rect#contains(int, int)} */ contains(int x, int y)97 public boolean contains(int x, int y) { 98 return !isEmpty() && x >= left && x < right && y >= top && y < bottom; 99 } 100 101 /** See {@link android.graphics.Rect#isEmpty()} */ isEmpty()102 public boolean isEmpty() { 103 return (left == right) || (top == bottom); 104 } 105 106 /** 107 * Returns a {@link Rect} which encloses this rectangle and the specified rectangle. If the 108 * specified rectangle is empty, return {@code this}. If this rectangle is empty, return the 109 * specified rectangle. 110 * 111 * <p>See {@link android.graphics.Rect#union(android.graphics.Rect)} 112 * 113 * @param r The rectangle being unioned with this rectangle 114 */ union(Rect r)115 public Rect union(Rect r) { 116 return isEmpty() ? r : union(r.left, r.top, r.right, r.bottom); 117 } 118 119 /** 120 * Returns a {@link Rect} which encloses this rectangle and the specified rectangle. If the 121 * specified rectangle is empty, return {@code this}. If this rectangle is empty, return the 122 * specified rectangle. 123 * 124 * <p>See {@link android.graphics.Rect#union(int, int, int, int)} 125 * 126 * @param left The left edge being unioned with this rectangle 127 * @param top The top edge being unioned with this rectangle 128 * @param right The right edge being unioned with this rectangle 129 * @param bottom The bottom edge being unioned with this rectangle 130 */ union(int left, int top, int right, int bottom)131 public Rect union(int left, int top, int right, int bottom) { 132 if ((right <= left) || (bottom <= top)) { 133 return this; 134 } 135 136 if (isEmpty()) { 137 return new Rect(left, top, right, bottom); 138 } else { 139 return new Rect( 140 min(left, this.left), 141 min(top, this.top), 142 max(right, this.right), 143 max(bottom, this.bottom)); 144 } 145 } 146 147 /** 148 * Returns a {@link Rect} that represents the intersection of this rectangle and the specific 149 * rectangle. If there is no intersection, an empty rectangle is returned. 150 * 151 * @param r The rectangle being intersected with this rectangle 152 */ intersect(Rect r)153 public Rect intersect(Rect r) { 154 if (!Rect.intersects(this, r)) { 155 return Rect.EMPTY; 156 } 157 158 return new Rect( 159 max(left, r.getLeft()), 160 max(top, r.getTop()), 161 min(right, r.getRight()), 162 min(bottom, r.getBottom())); 163 } 164 toProto()165 public RectProto toProto() { 166 return RectProto.newBuilder() 167 .setLeft(left) 168 .setTop(top) 169 .setRight(right) 170 .setBottom(bottom) 171 .build(); 172 } 173 174 @Override equals(@ullable Object o)175 public boolean equals(@Nullable Object o) { 176 if (this == o) { 177 return true; 178 } 179 180 if (o == null || Rect.class != o.getClass()) { 181 return false; 182 } 183 184 Rect r = (Rect) o; 185 return ((left == r.left) && (top == r.top) && (right == r.right) && (bottom == r.bottom)); 186 } 187 188 @Override hashCode()189 public int hashCode() { 190 int result = left; 191 result = 31 * result + top; 192 result = 31 * result + right; 193 result = 31 * result + bottom; 194 return result; 195 } 196 197 @Override toString()198 public String toString() { 199 return "Rect(" + left + ", " + top + " - " + right + ", " + bottom + ")"; 200 } 201 202 /** See {@link android.graphics.Rect#toShortString()} */ toShortString()203 public String toShortString() { 204 return "[" + left + ',' + top + "][" + right + ',' + bottom + ']'; 205 } 206 207 /** See {@link android.graphics.Rect#flattenToString()} */ flattenToString()208 public String flattenToString() { 209 return Integer.toString(left) + ' ' + top + ' ' + right + ' ' + bottom; 210 } 211 212 /** See {@link android.graphics.Rect#unflattenFromString()} */ unflattenFromString(@ullable String str)213 public static @Nullable Rect unflattenFromString(@Nullable String str) { 214 if (TextUtils.isEmpty(str)) { 215 return null; 216 } 217 218 Matcher matcher = FLATTENED_PATTERN.matcher(str); 219 if (matcher.matches()) { 220 return new Rect( 221 Integer.parseInt(checkNotNull(matcher.group(1))), 222 Integer.parseInt(checkNotNull(matcher.group(2))), 223 Integer.parseInt(checkNotNull(matcher.group(3))), 224 Integer.parseInt(checkNotNull(matcher.group(4)))); 225 } 226 return null; 227 } 228 229 /** See {@link android.graphics.Rect#intersects(android.graphics.Rect, android.graphics.Rect)} */ intersects(Rect a, Rect b)230 public static boolean intersects(Rect a, Rect b) { 231 return (a.top < b.bottom) && (b.top < a.bottom) && (a.left < b.right) && (b.left < a.right); 232 } 233 } 234