1 /*
2  * Copyright (C) 2022 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 com.android.wm.shell.compatui;
18 
19 import android.annotation.NonNull;
20 import android.app.TaskInfo;
21 import android.content.Context;
22 import android.content.SharedPreferences;
23 import android.provider.DeviceConfig;
24 
25 import com.android.wm.shell.R;
26 import com.android.wm.shell.common.ShellExecutor;
27 import com.android.wm.shell.dagger.WMSingleton;
28 import com.android.wm.shell.shared.annotations.ShellMainThread;
29 
30 import javax.inject.Inject;
31 
32 /**
33  * Configuration flags for the CompatUX implementation
34  */
35 @WMSingleton
36 public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
37 
38     private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG =
39             "enable_letterbox_restart_confirmation_dialog";
40 
41     private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
42             "enable_letterbox_education_for_reachability";
43 
44     private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG = true;
45 
46     private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = true;
47 
48     /**
49      * The name of the {@link SharedPreferences} that holds information about compat ui.
50      */
51     private static final String COMPAT_UI_SHARED_PREFERENCES = "dont_show_restart_dialog";
52 
53     /**
54      * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
55      * Education dialog.
56      */
57     private static final String HAS_SEEN_LETTERBOX_EDUCATION_SHARED_PREFERENCES =
58             "has_seen_letterbox_education";
59 
60     /**
61      * Key prefix for the {@link SharedPreferences} entries related to the horizontal
62      * reachability education.
63      */
64     private static final String HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX =
65             "has_seen_horizontal_reachability_education";
66 
67     /**
68      * Key prefix for the {@link SharedPreferences} entries related to the vertical reachability
69      * education.
70      */
71     private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
72             "has_seen_vertical_reachability_education";
73 
74     private static final int MAX_PERCENTAGE_VAL = 100;
75 
76     /**
77      * The {@link SharedPreferences} instance for the restart dialog and the reachability
78      * education.
79      */
80     private final SharedPreferences mCompatUISharedPreferences;
81 
82     /**
83      * The {@link SharedPreferences} instance for the letterbox education dialog.
84      */
85     private final SharedPreferences mLetterboxEduSharedPreferences;
86 
87     /**
88      * The minimum tolerance of the percentage of activity bounds within its task to hide
89      * size compat restart button.
90      */
91     private final int mHideSizeCompatRestartButtonTolerance;
92 
93     // Whether the extended restart dialog is enabled
94     private boolean mIsRestartDialogEnabled;
95 
96     // Whether the additional education about reachability is enabled
97     private boolean mIsReachabilityEducationEnabled;
98 
99     // Whether the extended restart dialog is enabled
100     private boolean mIsRestartDialogOverrideEnabled;
101 
102     // Whether the additional education about reachability is enabled
103     private boolean mIsReachabilityEducationOverrideEnabled;
104 
105     // Whether the extended restart dialog is allowed from backend
106     private boolean mIsLetterboxRestartDialogAllowed;
107 
108     // Whether the additional education about reachability is allowed from backend
109     private boolean mIsLetterboxReachabilityEducationAllowed;
110 
111     @Inject
CompatUIConfiguration(Context context, @ShellMainThread ShellExecutor mainExecutor)112     public CompatUIConfiguration(Context context, @ShellMainThread ShellExecutor mainExecutor) {
113         mIsRestartDialogEnabled = context.getResources().getBoolean(
114                 R.bool.config_letterboxIsRestartDialogEnabled);
115         mIsReachabilityEducationEnabled = context.getResources().getBoolean(
116                 R.bool.config_letterboxIsReachabilityEducationEnabled);
117         final int tolerance = context.getResources().getInteger(
118                 R.integer.config_letterboxRestartButtonHideTolerance);
119         mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
120         mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
121                 DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
122                 DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
123         mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
124                 DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
125                 DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
126         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
127                 this);
128         mCompatUISharedPreferences = context.getSharedPreferences(getCompatUISharedPreferenceName(),
129                 Context.MODE_PRIVATE);
130         mLetterboxEduSharedPreferences = context.getSharedPreferences(
131                 getHasSeenLetterboxEducationSharedPreferencedName(), Context.MODE_PRIVATE);
132     }
133 
134     /**
135      * @return {@value true} if the restart dialog is enabled.
136      */
isRestartDialogEnabled()137     boolean isRestartDialogEnabled() {
138         return mIsRestartDialogOverrideEnabled || (mIsRestartDialogEnabled
139                 && mIsLetterboxRestartDialogAllowed);
140     }
141 
142     /**
143      * Enables/Disables the restart education dialog
144      */
setIsRestartDialogOverrideEnabled(boolean enabled)145     void setIsRestartDialogOverrideEnabled(boolean enabled) {
146         mIsRestartDialogOverrideEnabled = enabled;
147     }
148 
149     /**
150      * Enables/Disables the reachability education
151      */
setIsReachabilityEducationOverrideEnabled(boolean enabled)152     void setIsReachabilityEducationOverrideEnabled(boolean enabled) {
153         mIsReachabilityEducationOverrideEnabled = enabled;
154     }
155 
setDontShowRestartDialogAgain(TaskInfo taskInfo)156     void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
157         mCompatUISharedPreferences.edit().putBoolean(
158                 dontShowAgainRestartKey(taskInfo.userId, taskInfo.topActivity.getPackageName()),
159                 true).apply();
160     }
161 
shouldShowRestartDialogAgain(TaskInfo taskInfo)162     boolean shouldShowRestartDialogAgain(TaskInfo taskInfo) {
163         return !mCompatUISharedPreferences.getBoolean(dontShowAgainRestartKey(taskInfo.userId,
164                 taskInfo.topActivity.getPackageName()), /* default= */ false);
165     }
166 
setUserHasSeenHorizontalReachabilityEducation(TaskInfo taskInfo)167     void setUserHasSeenHorizontalReachabilityEducation(TaskInfo taskInfo) {
168         mCompatUISharedPreferences.edit().putBoolean(
169                 hasSeenHorizontalReachabilityEduKey(taskInfo.userId), true).apply();
170     }
171 
setUserHasSeenVerticalReachabilityEducation(TaskInfo taskInfo)172     void setUserHasSeenVerticalReachabilityEducation(TaskInfo taskInfo) {
173         mCompatUISharedPreferences.edit().putBoolean(
174                 hasSeenVerticalReachabilityEduKey(taskInfo.userId), true).apply();
175     }
176 
hasSeenHorizontalReachabilityEducation(@onNull TaskInfo taskInfo)177     boolean hasSeenHorizontalReachabilityEducation(@NonNull TaskInfo taskInfo) {
178         return mCompatUISharedPreferences.getBoolean(
179                 hasSeenHorizontalReachabilityEduKey(taskInfo.userId), /* default= */false);
180     }
181 
hasSeenVerticalReachabilityEducation(@onNull TaskInfo taskInfo)182     boolean hasSeenVerticalReachabilityEducation(@NonNull TaskInfo taskInfo) {
183         return mCompatUISharedPreferences.getBoolean(
184                 hasSeenVerticalReachabilityEduKey(taskInfo.userId), /* default= */false);
185     }
186 
shouldShowReachabilityEducation(@onNull TaskInfo taskInfo)187     boolean shouldShowReachabilityEducation(@NonNull TaskInfo taskInfo) {
188         return isReachabilityEducationEnabled()
189                 && (!hasSeenHorizontalReachabilityEducation(taskInfo)
190                     || !hasSeenVerticalReachabilityEducation(taskInfo));
191     }
192 
getHideSizeCompatRestartButtonTolerance()193     int getHideSizeCompatRestartButtonTolerance() {
194         return mHideSizeCompatRestartButtonTolerance;
195     }
196 
getDefaultHideRestartButtonTolerance()197     int getDefaultHideRestartButtonTolerance() {
198         return MAX_PERCENTAGE_VAL;
199     }
200 
getHasSeenLetterboxEducation(int userId)201     boolean getHasSeenLetterboxEducation(int userId) {
202         return mLetterboxEduSharedPreferences
203                 .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
204     }
205 
setSeenLetterboxEducation(int userId)206     void setSeenLetterboxEducation(int userId) {
207         mLetterboxEduSharedPreferences.edit().putBoolean(dontShowLetterboxEduKey(userId),
208                 true).apply();
209     }
210 
getCompatUISharedPreferenceName()211     protected String getCompatUISharedPreferenceName() {
212         return COMPAT_UI_SHARED_PREFERENCES;
213     }
214 
getHasSeenLetterboxEducationSharedPreferencedName()215     protected String getHasSeenLetterboxEducationSharedPreferencedName() {
216         return HAS_SEEN_LETTERBOX_EDUCATION_SHARED_PREFERENCES;
217     }
218 
219     /**
220      * Updates the {@link DeviceConfig} state for the CompatUI
221      * @param properties Contains the complete collection of properties which have changed for a
222      *                   single namespace. This includes only those which were added, updated,
223      */
224     @Override
onPropertiesChanged(@onNull DeviceConfig.Properties properties)225     public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
226         if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
227             mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
228                     DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
229                     DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
230         }
231         // TODO(b/263349751): Update flag and default value to true
232         if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) {
233             mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
234                     DeviceConfig.NAMESPACE_WINDOW_MANAGER,
235                     KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
236                     DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
237         }
238     }
239 
240     // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
241     // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
242     // 100 is the default value where the activity has to fit exactly within the task to allow
243     // size compat restart button to be hidden. 0 means size compat restart button will always
244     // be hidden.
getHideSizeCompatRestartButtonTolerance(int tolerance)245     private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
246         return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
247     }
248 
isReachabilityEducationEnabled()249     private boolean isReachabilityEducationEnabled() {
250         return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
251                 && mIsLetterboxReachabilityEducationAllowed);
252     }
253 
hasSeenHorizontalReachabilityEduKey(int userId)254     private static String hasSeenHorizontalReachabilityEduKey(int userId) {
255         return HAS_SEEN_HORIZONTAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
256     }
257 
hasSeenVerticalReachabilityEduKey(int userId)258     private static String hasSeenVerticalReachabilityEduKey(int userId) {
259         return HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX + "@" + userId;
260     }
261 
dontShowLetterboxEduKey(int userId)262     private static String dontShowLetterboxEduKey(int userId) {
263         return String.valueOf(userId);
264     }
265 
dontShowAgainRestartKey(int userId, String packageName)266     private String dontShowAgainRestartKey(int userId, String packageName) {
267         return packageName + "@" + userId;
268     }
269 }