1 /*
2  * Copyright (C) 2018 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 android.car.drivingstate;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 
31 /**
32  * Car UX Restrictions event.  This contains information on the set of UX restrictions that is in
33  * place due to the car's driving state.
34  * <p>
35  * The restriction information is organized as follows:
36  * <ul>
37  * <li> When there are no restrictions in place, for example when the car is parked,
38  * <ul>
39  * <li> {@link #isRequiresDistractionOptimization()} returns false.  Apps can display activities
40  * that are not distraction optimized.
41  * <li> When {@link #isRequiresDistractionOptimization()} returns false, apps don't have to call
42  * {@link #getActiveRestrictions()}, since there is no distraction optimization required.
43  * </ul>
44  * <li> When the driving state changes, causing the UX restrictions to come in effect,
45  * <ul>
46  * <li> {@link #isRequiresDistractionOptimization()} returns true.  Apps can only display activities
47  * that are distraction optimized.  Distraction optimized activities must follow the base design
48  * guidelines to ensure a distraction free driving experience for the user.
49  * <li> When {@link #isRequiresDistractionOptimization()} returns true, apps must call
50  * {@link #getActiveRestrictions()}, to get the currently active UX restrictions to adhere to.
51  * {@link #getActiveRestrictions()} provides additional information on the set of UX
52  * restrictions that are in place for the current driving state.
53  * <p>
54  * The UX restrictions returned by {@link #getActiveRestrictions()}, for the same driving state of
55  * the vehicle, could vary depending on the OEM and the market.  For example, when the car is
56  * idling, the set of active UX restrictions will depend on the car maker and the safety standards
57  * of the market that the vehicle is deployed in.
58  * </ul>
59  * </ul>
60  * <p>
61  * Apps that intend to be run when the car is being driven need to
62  * <ul>
63  * <li> Comply with the general distraction optimization guidelines.
64  * <li> Listen and react to the UX restrictions changes as detailed above.  Since the restrictions
65  * could vary depending on the market, apps are expected to react to the restriction information
66  * and not to the absolute driving state.
67  * </ul>
68  */
69 public final class CarUxRestrictions implements Parcelable {
70 
71     // Default fallback values for the restriction related parameters if the information is
72     // not available from the underlying service.
73     private static final int DEFAULT_MAX_LENGTH = 120;
74     private static final int DEFAULT_MAX_CUMULATIVE_ITEMS = 21;
75     private static final int DEFAULT_MAX_CONTENT_DEPTH = 3;
76 
77     /**
78      * No specific restrictions in place, but baseline distraction optimization guidelines need to
79      * be adhered to when {@link #isRequiresDistractionOptimization()} is true.
80      */
81     public static final int UX_RESTRICTIONS_BASELINE = 0;
82 
83     // Granular UX Restrictions that are imposed when distraction optimization is required.
84     /**
85      * No dialpad for the purpose of initiating a phone call.
86      */
87     public static final int UX_RESTRICTIONS_NO_DIALPAD = 1;
88 
89     /**
90      * No filtering a list with alpha-numeric character via the use of a character entry method.
91      *
92      * For example, do not allow entering a letter to filter the content of a list down to
93      * items only containing that letter.
94      */
95     public static final int UX_RESTRICTIONS_NO_FILTERING = 0x1 << 1;
96 
97     /**
98      * General purpose strings length cannot exceed the character limit provided by
99      * {@link #getMaxRestrictedStringLength()}
100      */
101     public static final int UX_RESTRICTIONS_LIMIT_STRING_LENGTH = 0x1 << 2;
102 
103     /**
104      * No text entry for the purpose of searching or other manual text string entry actvities.
105      */
106     public static final int UX_RESTRICTIONS_NO_KEYBOARD = 0x1 << 3;
107 
108     /**
109      * No video - no animated frames > 1fps.
110      */
111     public static final int UX_RESTRICTIONS_NO_VIDEO = 0x1 << 4;
112 
113     /**
114      * Limit the number of items user can browse through in total in a single task.
115      *
116      * <p>Refer to {@link #getMaxCumulativeContentItems()} and
117      * {@link #getMaxContentDepth()} for the upper bounds on content
118      * serving.
119      */
120     public static final int UX_RESTRICTIONS_LIMIT_CONTENT = 0x1 << 5;
121 
122     /**
123      * No setup that requires form entry or interaction with external devices.
124      */
125     public static final int UX_RESTRICTIONS_NO_SETUP = 0x1 << 6;
126 
127     /**
128      * No Text Message (SMS, email, conversational, etc.)
129      */
130     public static final int UX_RESTRICTIONS_NO_TEXT_MESSAGE = 0x1 << 7;
131 
132     /**
133      * No text transcription (live or leave behind) of voice can be shown.
134      */
135     public static final int UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION = 0x1 << 8;
136 
137     /**
138      * All restrictions are in effect.
139      */
140     public static final int UX_RESTRICTIONS_FULLY_RESTRICTED =
141             UX_RESTRICTIONS_NO_DIALPAD | UX_RESTRICTIONS_NO_FILTERING
142                     | UX_RESTRICTIONS_LIMIT_STRING_LENGTH | UX_RESTRICTIONS_NO_KEYBOARD
143                     | UX_RESTRICTIONS_NO_VIDEO | UX_RESTRICTIONS_LIMIT_CONTENT
144                     | UX_RESTRICTIONS_NO_SETUP | UX_RESTRICTIONS_NO_TEXT_MESSAGE
145                     | UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION;
146 
147     /** @removed accidentally exposed previously */
148     @IntDef(flag = true,
149             prefix = {"UX_RESTRICTIONS_"},
150             value = {UX_RESTRICTIONS_BASELINE,
151                     UX_RESTRICTIONS_NO_DIALPAD,
152                     UX_RESTRICTIONS_NO_FILTERING,
153                     UX_RESTRICTIONS_LIMIT_STRING_LENGTH,
154                     UX_RESTRICTIONS_NO_KEYBOARD,
155                     UX_RESTRICTIONS_NO_VIDEO,
156                     UX_RESTRICTIONS_LIMIT_CONTENT,
157                     UX_RESTRICTIONS_NO_SETUP,
158                     UX_RESTRICTIONS_NO_TEXT_MESSAGE,
159                     UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION})
160     @Retention(RetentionPolicy.SOURCE)
161     public @interface CarUxRestrictionsInfo {
162     }
163 
164     private final long mTimeStamp;
165     private final boolean mRequiresDistractionOptimization;
166     @CarUxRestrictionsInfo
167     private final int mActiveRestrictions;
168     // Restriction Parameters
169     private final int mMaxStringLength;
170     private final int mMaxCumulativeContentItems;
171     private final int mMaxContentDepth;
172 
173     /**
174      * Builder class for {@link CarUxRestrictions}
175      */
176     public static class Builder {
177         private final long mTimeStamp;
178         private final boolean mRequiresDistractionOptimization;
179         @CarUxRestrictionsInfo
180         private final int mActiveRestrictions;
181         // Restriction Parameters
182         private int mMaxStringLength = DEFAULT_MAX_LENGTH;
183         private int mMaxCumulativeContentItems = DEFAULT_MAX_CUMULATIVE_ITEMS;
184         private int mMaxContentDepth = DEFAULT_MAX_CONTENT_DEPTH;
185 
Builder(boolean reqOpt, @CarUxRestrictionsInfo int restrictions, long time)186         public Builder(boolean reqOpt, @CarUxRestrictionsInfo int restrictions, long time) {
187             mRequiresDistractionOptimization = reqOpt;
188             mActiveRestrictions = restrictions;
189             mTimeStamp = time;
190         }
191 
192         /**
193          * Set the maximum length of general purpose strings that can be displayed when
194          * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_STRING_LENGTH} is imposed.
195          */
setMaxStringLength(int length)196         public Builder setMaxStringLength(int length) {
197             mMaxStringLength = length;
198             return this;
199         }
200 
201         /**
202          *  Set the maximum number of cumulative content items that can be displayed when
203          * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
204          */
setMaxCumulativeContentItems(int number)205         public Builder setMaxCumulativeContentItems(int number) {
206             mMaxCumulativeContentItems = number;
207             return this;
208         }
209 
210         /**
211          * Set the maximum number of levels that the user can navigate to when
212          * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
213          */
setMaxContentDepth(int depth)214         public Builder setMaxContentDepth(int depth) {
215             mMaxContentDepth = depth;
216             return this;
217         }
218 
219         /**
220          * Build and return the {@link CarUxRestrictions} object
221          */
build()222         public CarUxRestrictions build() {
223             return new CarUxRestrictions(this);
224         }
225 
226     }
227 
228     /**
229      * Time at which this UX restriction event was deduced based on the car's driving state.
230      *
231      * @return Elapsed time in nanoseconds since system boot.
232      *
233      * @hide
234      */
getTimeStamp()235     public long getTimeStamp() {
236         return mTimeStamp;
237     }
238 
239     /**
240      * Conveys if the foreground activity needs to be distraction optimized.
241      * Activities that can handle distraction optimization need to be tagged as a distraction
242      * optimized in the app's manifest.
243      * <p>
244      * If the app has a foreground activity that has not been distraction optimized, the app has
245      * to switch to another activity that is distraction optimized.  Failing that, the system will
246      * stop the foreground activity.
247      *
248      * @return true if distraction optimization is required, false if not
249      */
isRequiresDistractionOptimization()250     public boolean isRequiresDistractionOptimization() {
251         return mRequiresDistractionOptimization;
252     }
253 
254     /**
255      * A combination of the Car UX Restrictions that is active for the current state of driving.
256      *
257      * @return A combination of the above {@code @CarUxRestrictionsInfo}
258      */
259     @CarUxRestrictionsInfo
getActiveRestrictions()260     public int getActiveRestrictions() {
261         return mActiveRestrictions;
262     }
263 
264     /**
265      * Get the maximum length of general purpose strings that can be displayed when
266      * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_STRING_LENGTH} is imposed.
267      *
268      * @return the maximum length of string that can be displayed
269      */
getMaxRestrictedStringLength()270     public int getMaxRestrictedStringLength() {
271         return mMaxStringLength;
272     }
273 
274     /**
275      * Get the maximum allowable number of content items that can be displayed to a user during
276      * traversal through any one path in a single task, when
277      * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
278      * <p>
279      * For example, if a task involving only one view, this represents the maximum allowable number
280      * of content items in this single view.
281      * <p>
282      * However, if the task involves selection of a content item in an originating view that then
283      * surfaces a secondary view to the user, then this value represents the maximum allowable
284      * number of content items between the originating and secondary views combined.
285      * <p>
286      * Specifically, if the maximum allowable value was 60 and a task involved browsing a list of
287      * countries and then viewing the top songs within a country, it would be acceptable to do
288      * either of the following:
289      * <ul>
290      * <li> list 10 countries, and then display the top 50 songs after country selection, or
291      * <li> list 20 countries, and then display the top 40 songs after country selection.
292      * </ul>
293      * <p>
294      * Please refer to this and {@link #getMaxContentDepth()} to know the upper bounds on the
295      * content display when the restriction is in place.
296      *
297      * @return maximum number of cumulative items that can be displayed
298      */
getMaxCumulativeContentItems()299     public int getMaxCumulativeContentItems() {
300         return mMaxCumulativeContentItems;
301     }
302 
303     /**
304      * Get the maximum allowable number of content depth levels or view traversals through any one
305      * path in a single task.  This is applicable when
306      * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
307      * <p>
308      * For example, if a task involves only selecting an item from a single list on one view,
309      * the task's content depth would be considered 1.
310      * <p>
311      * However, if the task involves selection of a content item in an originating view that then
312      * surfaces a secondary view to the user, the task's content depth would be considered 2.
313      * <p>
314      * Specifically, if a task involved browsing a list of countries, selecting a genre within the
315      * country, and then viewing the top songs within a country, the task's content depth would be
316      * considered 3.
317      * <p>
318      * Please refer to this and {@link #getMaxCumulativeContentItems()} to know the upper bounds on
319      * the content display when the restriction is in place.
320      *
321      * @return maximum levels deep that the user can navigate to
322      */
getMaxContentDepth()323     public int getMaxContentDepth() {
324         return mMaxContentDepth;
325     }
326 
327     @Override
328     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
describeContents()329     public int describeContents() {
330         return 0;
331     }
332 
333     @Override
writeToParcel(Parcel dest, int flags)334     public void writeToParcel(Parcel dest, int flags) {
335         dest.writeInt(mActiveRestrictions);
336         dest.writeLong(mTimeStamp);
337         dest.writeInt(mRequiresDistractionOptimization ? 1 : 0);
338         dest.writeInt(mMaxStringLength);
339         dest.writeInt(mMaxCumulativeContentItems);
340         dest.writeInt(mMaxContentDepth);
341     }
342 
343     public static final Parcelable.Creator<CarUxRestrictions> CREATOR =
344             new Parcelable.Creator<CarUxRestrictions>() {
345         @Override
346         public CarUxRestrictions createFromParcel(Parcel in) {
347             return new CarUxRestrictions(in);
348         }
349 
350         @Override
351         public CarUxRestrictions[] newArray(int size) {
352             return new CarUxRestrictions[size];
353         }
354     };
355 
CarUxRestrictions(CarUxRestrictions uxRestrictions)356     public CarUxRestrictions(CarUxRestrictions uxRestrictions) {
357         mTimeStamp = uxRestrictions.getTimeStamp();
358         mRequiresDistractionOptimization = uxRestrictions.isRequiresDistractionOptimization();
359         mActiveRestrictions = uxRestrictions.getActiveRestrictions();
360         mMaxStringLength = uxRestrictions.mMaxStringLength;
361         mMaxCumulativeContentItems = uxRestrictions.mMaxCumulativeContentItems;
362         mMaxContentDepth = uxRestrictions.mMaxContentDepth;
363     }
364 
CarUxRestrictions(Builder builder)365     private CarUxRestrictions(Builder builder) {
366         mTimeStamp = builder.mTimeStamp;
367         mActiveRestrictions = builder.mActiveRestrictions;
368         mRequiresDistractionOptimization = builder.mRequiresDistractionOptimization;
369         mMaxStringLength = builder.mMaxStringLength;
370         mMaxCumulativeContentItems = builder.mMaxCumulativeContentItems;
371         mMaxContentDepth = builder.mMaxContentDepth;
372     }
373 
CarUxRestrictions(Parcel in)374     private CarUxRestrictions(Parcel in) {
375         mActiveRestrictions = in.readInt();
376         mTimeStamp = in.readLong();
377         mRequiresDistractionOptimization = in.readInt() != 0;
378         mMaxStringLength = in.readInt();
379         mMaxCumulativeContentItems = in.readInt();
380         mMaxContentDepth = in.readInt();
381     }
382 
383     @Override
toString()384     public String toString() {
385         return "DO: " + mRequiresDistractionOptimization + " UxR: " + mActiveRestrictions
386                 + " time: " + mTimeStamp;
387     }
388 
389     /**
390      * Returns active restrictions string.
391      * @hide
392      */
393     @NonNull
getActiveRestrictionsString()394     public String getActiveRestrictionsString() {
395         StringBuilder sb = new StringBuilder();
396 
397         if (mActiveRestrictions == UX_RESTRICTIONS_BASELINE) {
398             sb.append("baseline\n");
399             return sb.toString();
400         }
401 
402         if ((mActiveRestrictions
403                 & UX_RESTRICTIONS_NO_DIALPAD) == UX_RESTRICTIONS_NO_DIALPAD) {
404             sb.append("no_dialpad\n");
405         }
406 
407         if ((mActiveRestrictions
408                 & UX_RESTRICTIONS_NO_FILTERING) == UX_RESTRICTIONS_NO_FILTERING) {
409             sb.append("no_filtering\n");
410         }
411 
412         if ((mActiveRestrictions
413                 & UX_RESTRICTIONS_NO_KEYBOARD) == UX_RESTRICTIONS_NO_KEYBOARD) {
414             sb.append("no_keyboard\n");
415         }
416 
417         if ((mActiveRestrictions
418                 & UX_RESTRICTIONS_NO_SETUP) == UX_RESTRICTIONS_NO_SETUP) {
419             sb.append("no_setup\n");
420         }
421 
422         if ((mActiveRestrictions
423                 & UX_RESTRICTIONS_NO_TEXT_MESSAGE) == UX_RESTRICTIONS_NO_TEXT_MESSAGE) {
424             sb.append("no_text_message\n");
425         }
426 
427         if ((mActiveRestrictions
428                 & UX_RESTRICTIONS_NO_VIDEO) == UX_RESTRICTIONS_NO_VIDEO) {
429             sb.append("no_video\n");
430         }
431 
432         if ((mActiveRestrictions
433                 & UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION)
434                 == UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION) {
435             sb.append("no_voice_transcription\n");
436         }
437         return sb.toString();
438     }
439 
440     /**
441      * Compares if the restrictions are the same.  Doesn't compare the timestamps.
442      *
443      * @param other the other CarUxRestrictions object
444      * @return true if the restrictions are same, false otherwise
445      */
isSameRestrictions(CarUxRestrictions other)446     public boolean isSameRestrictions(CarUxRestrictions other) {
447         if (other == null) {
448             return false;
449         }
450         if (other == this) {
451             return true;
452         }
453         return other.mRequiresDistractionOptimization == mRequiresDistractionOptimization
454                 && other.mActiveRestrictions == mActiveRestrictions;
455     }
456 }
457