1 /*
2  * Copyright (C) 2014 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.systemui.statusbar.phone;
18 
19 import android.content.Context;
20 import android.os.PowerManager;
21 import android.os.SystemProperties;
22 import android.os.UserHandle;
23 import android.provider.Settings;
24 import android.text.TextUtils;
25 import android.util.MathUtils;
26 import android.util.SparseBooleanArray;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.hardware.AmbientDisplayConfiguration;
30 import com.android.systemui.Dependency;
31 import com.android.systemui.R;
32 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
33 import com.android.systemui.doze.DozeScreenState;
34 import com.android.systemui.tuner.TunerService;
35 
36 import java.io.PrintWriter;
37 
38 public class DozeParameters implements TunerService.Tunable {
39     private static final int MAX_DURATION = 60 * 1000;
40     public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully";
41     public static final boolean FORCE_NO_BLANKING =
42             SystemProperties.getBoolean("debug.force_no_blanking", false);
43 
44     private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
45     private static DozeParameters sInstance;
46 
47     private final Context mContext;
48     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
49     private final PowerManager mPowerManager;
50 
51     private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
52 
53     private boolean mDozeAlwaysOn;
54     private boolean mControlScreenOffAnimation;
55 
getInstance(Context context)56     public static DozeParameters getInstance(Context context) {
57         if (sInstance == null) {
58             sInstance = new DozeParameters(context);
59         }
60         return sInstance;
61     }
62 
63     @VisibleForTesting
DozeParameters(Context context)64     protected DozeParameters(Context context) {
65         mContext = context.getApplicationContext();
66         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
67         mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(mContext);
68 
69         mControlScreenOffAnimation = !getDisplayNeedsBlanking();
70         mPowerManager = mContext.getSystemService(PowerManager.class);
71         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
72 
73         Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON,
74                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
75     }
76 
dump(PrintWriter pw)77     public void dump(PrintWriter pw) {
78         pw.println("  DozeParameters:");
79         pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
80         pw.print("    getPulseDuration(): "); pw.println(getPulseDuration());
81         pw.print("    getPulseInDuration(): "); pw.println(getPulseInDuration());
82         pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
83         pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
84         pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
85         pw.print("    getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
86         pw.print("    getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
87         pw.print("    getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
88         pw.print("    getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
89         pw.print("    getPickupSubtypePerformsProxCheck(): ");pw.println(
90                 dumpPickupSubtypePerformsProxCheck());
91     }
92 
dumpPickupSubtypePerformsProxCheck()93     private String dumpPickupSubtypePerformsProxCheck() {
94         // Refresh sPickupSubtypePerformsProxMatcher
95         getPickupSubtypePerformsProxCheck(0);
96 
97         if (sPickupSubtypePerformsProxMatcher == null) {
98             return "fallback: " + mContext.getResources().getBoolean(
99                     R.bool.doze_pickup_performs_proximity_check);
100         } else {
101             return "spec: " + sPickupSubtypePerformsProxMatcher.mSpec;
102         }
103     }
104 
getDisplayStateSupported()105     public boolean getDisplayStateSupported() {
106         return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
107     }
108 
getDozeSuspendDisplayStateSupported()109     public boolean getDozeSuspendDisplayStateSupported() {
110         return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
111     }
112 
getPulseDuration()113     public int getPulseDuration() {
114         return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
115     }
116 
getScreenBrightnessDoze()117     public float getScreenBrightnessDoze() {
118         return mContext.getResources().getInteger(
119                 com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
120     }
121 
getPulseInDuration()122     public int getPulseInDuration() {
123         return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
124     }
125 
getPulseVisibleDuration()126     public int getPulseVisibleDuration() {
127         return getInt("doze.pulse.duration.visible", R.integer.doze_pulse_duration_visible);
128     }
129 
getPulseOutDuration()130     public int getPulseOutDuration() {
131         return getInt("doze.pulse.duration.out", R.integer.doze_pulse_duration_out);
132     }
133 
getPulseOnSigMotion()134     public boolean getPulseOnSigMotion() {
135         return getBoolean("doze.pulse.sigmotion", R.bool.doze_pulse_on_significant_motion);
136     }
137 
getVibrateOnSigMotion()138     public boolean getVibrateOnSigMotion() {
139         return SystemProperties.getBoolean("doze.vibrate.sigmotion", false);
140     }
141 
getVibrateOnPickup()142     public boolean getVibrateOnPickup() {
143         return SystemProperties.getBoolean("doze.vibrate.pickup", false);
144     }
145 
getProxCheckBeforePulse()146     public boolean getProxCheckBeforePulse() {
147         return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
148     }
149 
getPickupVibrationThreshold()150     public int getPickupVibrationThreshold() {
151         return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
152     }
153 
154     /**
155      * For how long a wallpaper can be visible in AoD before it fades aways.
156      * @return duration in millis.
157      */
getWallpaperAodDuration()158     public long getWallpaperAodDuration() {
159         return shouldControlScreenOff() ? DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY
160                 : mAlwaysOnPolicy.wallpaperVisibilityDuration;
161     }
162 
163     /**
164      * How long it takes for the wallpaper fade away (Animation duration.)
165      * @return duration in millis.
166      */
getWallpaperFadeOutDuration()167     public long getWallpaperFadeOutDuration() {
168         return mAlwaysOnPolicy.wallpaperFadeOutDuration;
169     }
170 
171     /**
172      * Checks if always on is available and enabled for the current user.
173      * @return {@code true} if enabled and available.
174      */
getAlwaysOn()175     public boolean getAlwaysOn() {
176         return mDozeAlwaysOn;
177     }
178 
179     /**
180      * Some screens need to be completely black before changing the display power mode,
181      * unexpected behavior might happen if this parameter isn't respected.
182      *
183      * @return {@code true} if screen needs to be completely black before a power transition.
184      */
getDisplayNeedsBlanking()185     public boolean getDisplayNeedsBlanking() {
186         return !FORCE_NO_BLANKING && mContext.getResources().getBoolean(
187                 com.android.internal.R.bool.config_displayBlanksAfterDoze);
188     }
189 
shouldControlScreenOff()190     public boolean shouldControlScreenOff() {
191         return mControlScreenOffAnimation;
192     }
193 
setControlScreenOffAnimation(boolean controlScreenOffAnimation)194     public void setControlScreenOffAnimation(boolean controlScreenOffAnimation) {
195         if (mControlScreenOffAnimation == controlScreenOffAnimation) {
196             return;
197         }
198         mControlScreenOffAnimation = controlScreenOffAnimation;
199         getPowerManager().setDozeAfterScreenOff(!controlScreenOffAnimation);
200     }
201 
202     @VisibleForTesting
getPowerManager()203     protected PowerManager getPowerManager() {
204         return mPowerManager;
205     }
206 
getBoolean(String propName, int resId)207     private boolean getBoolean(String propName, int resId) {
208         return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
209     }
210 
getInt(String propName, int resId)211     private int getInt(String propName, int resId) {
212         int value = SystemProperties.getInt(propName, mContext.getResources().getInteger(resId));
213         return MathUtils.constrain(value, 0, MAX_DURATION);
214     }
215 
getString(String propName, int resId)216     private String getString(String propName, int resId) {
217         return SystemProperties.get(propName, mContext.getString(resId));
218     }
219 
getPickupSubtypePerformsProxCheck(int subType)220     public boolean getPickupSubtypePerformsProxCheck(int subType) {
221         String spec = getString("doze.pickup.proxcheck",
222                 R.string.doze_pickup_subtype_performs_proximity_check);
223 
224         if (TextUtils.isEmpty(spec)) {
225             // Fall back to non-subtype based property.
226             return mContext.getResources().getBoolean(R.bool.doze_pickup_performs_proximity_check);
227         }
228 
229         if (sPickupSubtypePerformsProxMatcher == null
230                 || !TextUtils.equals(spec, sPickupSubtypePerformsProxMatcher.mSpec)) {
231             sPickupSubtypePerformsProxMatcher = new IntInOutMatcher(spec);
232         }
233 
234         return sPickupSubtypePerformsProxMatcher.isIn(subType);
235     }
236 
getPulseVisibleDurationExtended()237     public int getPulseVisibleDurationExtended() {
238         return 2 * getPulseVisibleDuration();
239     }
240 
doubleTapReportsTouchCoordinates()241     public boolean doubleTapReportsTouchCoordinates() {
242         return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
243     }
244 
245     @Override
onTuningChanged(String key, String newValue)246     public void onTuningChanged(String key, String newValue) {
247         mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
248     }
249 
getPolicy()250     public AlwaysOnDisplayPolicy getPolicy() {
251         return mAlwaysOnPolicy;
252     }
253 
254     /**
255      * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are
256      * listed, will not match numbers that are listed with a ! prefix, and will match / not match
257      * unlisted numbers depending on whether * or !* is present.
258      *
259      * *  -> match any numbers that are not explicitly listed
260      * !* -> don't match any numbers that are not explicitly listed
261      * 2  -> match 2
262      * !3 -> don't match 3
263      *
264      * It is illegal to specify:
265      * - an empty spec
266      * - a spec containing that are empty, or a lone !
267      * - a spec for anything other than numbers or *
268      * - multiple terms for the same number / multiple *s
269      */
270     public static class IntInOutMatcher {
271         private static final String WILDCARD = "*";
272         private static final char OUT_PREFIX = '!';
273 
274         private final SparseBooleanArray mIsIn;
275         private final boolean mDefaultIsIn;
276         final String mSpec;
277 
IntInOutMatcher(String spec)278         public IntInOutMatcher(String spec) {
279             if (TextUtils.isEmpty(spec)) {
280                 throw new IllegalArgumentException("Spec must not be empty");
281             }
282 
283             boolean defaultIsIn = false;
284             boolean foundWildcard = false;
285 
286             mSpec = spec;
287             mIsIn = new SparseBooleanArray();
288 
289             for (String itemPrefixed : spec.split(",", -1)) {
290                 if (itemPrefixed.length() == 0) {
291                     throw new IllegalArgumentException(
292                             "Illegal spec, must not have zero-length items: `" + spec + "`");
293                 }
294                 boolean isIn = itemPrefixed.charAt(0) != OUT_PREFIX;
295                 String item = isIn ? itemPrefixed : itemPrefixed.substring(1);
296 
297                 if (itemPrefixed.length() == 0) {
298                     throw new IllegalArgumentException(
299                             "Illegal spec, must not have zero-length items: `" + spec + "`");
300                 }
301 
302                 if (WILDCARD.equals(item)) {
303                     if (foundWildcard) {
304                         throw new IllegalArgumentException("Illegal spec, `" + WILDCARD +
305                                 "` must not appear multiple times in `" + spec + "`");
306                     }
307                     defaultIsIn = isIn;
308                     foundWildcard = true;
309                 } else {
310                     int key = Integer.parseInt(item);
311                     if (mIsIn.indexOfKey(key) >= 0) {
312                         throw new IllegalArgumentException("Illegal spec, `" + key +
313                                 "` must not appear multiple times in `" + spec + "`");
314                     }
315                     mIsIn.put(key, isIn);
316                 }
317             }
318 
319             if (!foundWildcard) {
320                 throw new IllegalArgumentException("Illegal spec, must specify either * or !*");
321             }
322 
323             mDefaultIsIn = defaultIsIn;
324         }
325 
isIn(int value)326         public boolean isIn(int value) {
327             return (mIsIn.get(value, mDefaultIsIn));
328         }
329     }
330 }
331