1 /* 2 * Copyright (C) 2021 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 androidx.window.common; 18 19 import static androidx.window.util.ExtensionHelper.isZero; 20 21 import android.annotation.Nullable; 22 import android.graphics.Rect; 23 24 import androidx.annotation.NonNull; 25 26 import java.util.Objects; 27 import java.util.regex.Matcher; 28 import java.util.regex.Pattern; 29 30 /** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ 31 final class CommonDisplayFeature implements DisplayFeature { 32 private static final Pattern FEATURE_PATTERN = 33 Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?"); 34 35 private static final String FEATURE_TYPE_FOLD = "fold"; 36 private static final String FEATURE_TYPE_HINGE = "hinge"; 37 38 private static final String PATTERN_STATE_FLAT = "flat"; 39 private static final String PATTERN_STATE_HALF_OPENED = "half-opened"; 40 41 // TODO(b/183049815): Support feature strings that include the state of the feature. 42 43 /** 44 * Parses a display feature from a string. 45 * 46 * @throws IllegalArgumentException if the provided string is improperly formatted or could not 47 * otherwise be parsed. 48 * @see #FEATURE_PATTERN 49 */ 50 @NonNull parseFromString(@onNull String string)51 static CommonDisplayFeature parseFromString(@NonNull String string) { 52 Matcher featureMatcher = FEATURE_PATTERN.matcher(string); 53 if (!featureMatcher.matches()) { 54 throw new IllegalArgumentException("Malformed feature description format: " + string); 55 } 56 try { 57 String featureType = featureMatcher.group(1); 58 featureType = featureType == null ? "" : featureType; 59 int type; 60 switch (featureType) { 61 case FEATURE_TYPE_FOLD: 62 type = 1 /* TYPE_FOLD */; 63 break; 64 case FEATURE_TYPE_HINGE: 65 type = 2 /* TYPE_HINGE */; 66 break; 67 default: { 68 throw new IllegalArgumentException("Malformed feature type: " + featureType); 69 } 70 } 71 72 int left = Integer.parseInt(featureMatcher.group(2)); 73 int top = Integer.parseInt(featureMatcher.group(3)); 74 int right = Integer.parseInt(featureMatcher.group(4)); 75 int bottom = Integer.parseInt(featureMatcher.group(5)); 76 Rect featureRect = new Rect(left, top, right, bottom); 77 if (isZero(featureRect)) { 78 throw new IllegalArgumentException("Feature has empty bounds: " + string); 79 } 80 String stateString = featureMatcher.group(6); 81 stateString = stateString == null ? "" : stateString; 82 Integer state; 83 switch (stateString) { 84 case PATTERN_STATE_FLAT: 85 state = COMMON_STATE_FLAT; 86 break; 87 case PATTERN_STATE_HALF_OPENED: 88 state = COMMON_STATE_HALF_OPENED; 89 break; 90 default: 91 state = null; 92 break; 93 } 94 return new CommonDisplayFeature(type, state, featureRect); 95 } catch (NumberFormatException e) { 96 throw new IllegalArgumentException("Malformed feature description: " + string, e); 97 } 98 } 99 100 private final int mType; 101 @Nullable 102 private final Integer mState; 103 @NonNull 104 private final Rect mRect; 105 CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect)106 CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) { 107 assertValidState(state); 108 this.mType = type; 109 this.mState = state; 110 if (rect.width() == 0 && rect.height() == 0) { 111 throw new IllegalArgumentException( 112 "Display feature rectangle cannot have zero width and height simultaneously."); 113 } 114 this.mRect = rect; 115 } 116 getType()117 public int getType() { 118 return mType; 119 } 120 121 /** Returns the state of the feature, or {@code null} if the feature has no state. */ 122 @Nullable getState()123 public Integer getState() { 124 return mState; 125 } 126 127 @NonNull getRect()128 public Rect getRect() { 129 return mRect; 130 } 131 132 @Override equals(Object o)133 public boolean equals(Object o) { 134 if (this == o) return true; 135 if (o == null || getClass() != o.getClass()) return false; 136 CommonDisplayFeature that = (CommonDisplayFeature) o; 137 return mType == that.mType 138 && Objects.equals(mState, that.mState) 139 && mRect.equals(that.mRect); 140 } 141 142 @Override hashCode()143 public int hashCode() { 144 return Objects.hash(mType, mState, mRect); 145 } 146 assertValidState(@ullable Integer state)147 private static void assertValidState(@Nullable Integer state) { 148 if (state != null && state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED) { 149 throw new IllegalArgumentException("Invalid state: " + state 150 + "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED"); 151 } 152 } 153 } 154