• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.IntDef;
22 import android.annotation.Nullable;
23 import android.graphics.Rect;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Objects;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 
36 /** A representation of a folding feature for both Extension and Sidecar.
37  * For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
38  * {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
39  * {@link androidx.window.extensions.layout.FoldingFeature}.
40  */
41 public final class CommonFoldingFeature {
42 
43     private static final boolean DEBUG = false;
44 
45     public static final String TAG = CommonFoldingFeature.class.getSimpleName();
46 
47     /**
48      * A common type to represent a hinge where the screen is continuous.
49      */
50     public static final int COMMON_TYPE_FOLD = 1;
51 
52     /**
53      * A common type to represent a hinge where there is a physical gap separating multiple
54      * displays.
55      */
56     public static final int COMMON_TYPE_HINGE = 2;
57 
58     @IntDef({COMMON_TYPE_FOLD, COMMON_TYPE_HINGE})
59     @Retention(RetentionPolicy.SOURCE)
60     public @interface Type {
61     }
62 
63     /**
64      * A common state to represent when the state is not known. One example is if the device is
65      * closed. We do not emit this value for developers but is useful for implementation reasons.
66      */
67     public static final int COMMON_STATE_UNKNOWN = -1;
68 
69     /**
70      * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
71      * and Extensions do not match exactly.
72      */
73     public static final int COMMON_STATE_FLAT = 3;
74     /**
75      * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
76      * Sidecar and Extensions do not match exactly.
77      */
78     public static final int COMMON_STATE_HALF_OPENED = 2;
79 
80     /**
81      * The possible states for a folding hinge.
82      */
83     @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
84     @Retention(RetentionPolicy.SOURCE)
85     public @interface State {
86     }
87 
88     private static final Pattern FEATURE_PATTERN =
89             Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?");
90 
91     private static final String FEATURE_TYPE_FOLD = "fold";
92     private static final String FEATURE_TYPE_HINGE = "hinge";
93 
94     private static final String PATTERN_STATE_FLAT = "flat";
95     private static final String PATTERN_STATE_HALF_OPENED = "half-opened";
96 
97     /**
98      * Parse a {@link List} of {@link CommonFoldingFeature} from a {@link String}.
99      * @param value a {@link String} representation of multiple {@link CommonFoldingFeature}
100      *              separated by a ":".
101      * @param hingeState a global fallback value for a {@link CommonFoldingFeature} if one is not
102      *                   specified in the input.
103      * @throws IllegalArgumentException if the provided string is improperly formatted or could not
104      * otherwise be parsed.
105      * @see #FEATURE_PATTERN
106      * @return {@link List} of {@link CommonFoldingFeature}.
107      */
parseListFromString(@onNull String value, @State int hingeState)108     static List<CommonFoldingFeature> parseListFromString(@NonNull String value,
109             @State int hingeState) {
110         List<CommonFoldingFeature> features = new ArrayList<>();
111         String[] featureStrings =  value.split(";");
112         for (String featureString : featureStrings) {
113             CommonFoldingFeature feature;
114             try {
115                 feature = CommonFoldingFeature.parseFromString(featureString, hingeState);
116             } catch (IllegalArgumentException e) {
117                 if (DEBUG) {
118                     Log.w(TAG, "Failed to parse display feature: " + featureString, e);
119                 }
120                 continue;
121             }
122             features.add(feature);
123         }
124         return features;
125     }
126 
127     /**
128      * Parses a display feature from a string.
129      *
130      * @param string A {@link String} representation of a {@link CommonFoldingFeature}.
131      * @param hingeState A fallback value for the {@link State} if it is not specified in the input.
132      * @throws IllegalArgumentException if the provided string is improperly formatted or could not
133      *                                  otherwise be parsed.
134      * @return {@link CommonFoldingFeature} represented by the {@link String} value.
135      * @see #FEATURE_PATTERN
136      */
137     @NonNull
parseFromString(@onNull String string, @State int hingeState)138     private static CommonFoldingFeature parseFromString(@NonNull String string,
139             @State int hingeState) {
140         Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
141         if (!featureMatcher.matches()) {
142             throw new IllegalArgumentException("Malformed feature description format: " + string);
143         }
144         try {
145             String featureType = featureMatcher.group(1);
146             featureType = featureType == null ? "" : featureType;
147             int type;
148             switch (featureType) {
149                 case FEATURE_TYPE_FOLD:
150                     type = COMMON_TYPE_FOLD;
151                     break;
152                 case FEATURE_TYPE_HINGE:
153                     type = COMMON_TYPE_HINGE;
154                     break;
155                 default: {
156                     throw new IllegalArgumentException("Malformed feature type: " + featureType);
157                 }
158             }
159 
160             int left = Integer.parseInt(featureMatcher.group(2));
161             int top = Integer.parseInt(featureMatcher.group(3));
162             int right = Integer.parseInt(featureMatcher.group(4));
163             int bottom = Integer.parseInt(featureMatcher.group(5));
164             Rect featureRect = new Rect(left, top, right, bottom);
165             if (isZero(featureRect)) {
166                 throw new IllegalArgumentException("Feature has empty bounds: " + string);
167             }
168             String stateString = featureMatcher.group(6);
169             stateString = stateString == null ? "" : stateString;
170             final int state;
171             switch (stateString) {
172                 case PATTERN_STATE_FLAT:
173                     state = COMMON_STATE_FLAT;
174                     break;
175                 case PATTERN_STATE_HALF_OPENED:
176                     state = COMMON_STATE_HALF_OPENED;
177                     break;
178                 default:
179                     state = hingeState;
180                     break;
181             }
182             return new CommonFoldingFeature(type, state, featureRect);
183         } catch (NumberFormatException e) {
184             throw new IllegalArgumentException("Malformed feature description: " + string, e);
185         }
186     }
187 
188     private final int mType;
189     @Nullable
190     private final int mState;
191     @NonNull
192     private final Rect mRect;
193 
CommonFoldingFeature(int type, int state, @NonNull Rect rect)194     CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
195         assertValidState(state);
196         this.mType = type;
197         this.mState = state;
198         if (rect.width() == 0 && rect.height() == 0) {
199             throw new IllegalArgumentException(
200                     "Display feature rectangle cannot have zero width and height simultaneously.");
201         }
202         this.mRect = new Rect(rect);
203     }
204 
205     /** Returns the type of the feature. */
206     @Type
getType()207     public int getType() {
208         return mType;
209     }
210 
211     /** Returns the state of the feature.*/
212     @State
getState()213     public int getState() {
214         return mState;
215     }
216 
217     /** Returns the bounds of the feature. */
218     @NonNull
getRect()219     public Rect getRect() {
220         return new Rect(mRect);
221     }
222 
223     @Override
equals(Object o)224     public boolean equals(Object o) {
225         if (this == o) return true;
226         if (o == null || getClass() != o.getClass()) return false;
227         CommonFoldingFeature that = (CommonFoldingFeature) o;
228         return mType == that.mType
229                 && Objects.equals(mState, that.mState)
230                 && mRect.equals(that.mRect);
231     }
232 
233     @Override
hashCode()234     public int hashCode() {
235         return Objects.hash(mType, mState, mRect);
236     }
237 
assertValidState(@ullable Integer state)238     private static void assertValidState(@Nullable Integer state) {
239         if (state != null && state != COMMON_STATE_FLAT
240                 && state != COMMON_STATE_HALF_OPENED && state != COMMON_STATE_UNKNOWN) {
241             throw new IllegalArgumentException("Invalid state: " + state
242                     + "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED");
243         }
244     }
245 }
246