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