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