DozeParameters.java revision 5753f05e85aa26fb370260e3dcfd44a27f8e43de
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
17package com.android.systemui.statusbar.phone;
18
19import android.content.Context;
20import android.os.SystemProperties;
21import android.text.TextUtils;
22import android.util.Log;
23import android.util.MathUtils;
24import android.util.SparseBooleanArray;
25
26import com.android.systemui.R;
27
28import java.io.PrintWriter;
29import java.util.Arrays;
30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
32
33public class DozeParameters {
34    private static final String TAG = "DozeParameters";
35    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
36
37    private static final int MAX_DURATION = 60 * 1000;
38
39    private final Context mContext;
40
41    private static PulseSchedule sPulseSchedule;
42
43    private static IntInOutMatcher sPickupSubtypePerformsProxMatcher;
44
45    public DozeParameters(Context context) {
46        mContext = context;
47    }
48
49    public void dump(PrintWriter pw) {
50        pw.println("  DozeParameters:");
51        pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
52        pw.print("    getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
53        pw.print("    getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
54        pw.print("    getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
55        pw.print("    getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
56        pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
57        pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
58        pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
59        pw.print("    getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
60        pw.print("    getPulseOnPickup(): "); pw.println(getPulseOnPickup());
61        pw.print("    getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
62        pw.print("    getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
63        pw.print("    getPulseOnNotifications(): "); pw.println(getPulseOnNotifications());
64        pw.print("    getPulseSchedule(): "); pw.println(getPulseSchedule());
65        pw.print("    getPulseScheduleResets(): "); pw.println(getPulseScheduleResets());
66        pw.print("    getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
67        pw.print("    getPickupSubtypePerformsProxCheck(): ");pw.println(
68                dumpPickupSubtypePerformsProxCheck());
69    }
70
71    private String dumpPickupSubtypePerformsProxCheck() {
72        // Refresh sPickupSubtypePerformsProxMatcher
73        getPickupSubtypePerformsProxCheck(0);
74
75        if (sPickupSubtypePerformsProxMatcher == null) {
76            return "fallback: " + mContext.getResources().getBoolean(
77                    R.bool.doze_pickup_performs_proximity_check);
78        } else {
79            return "spec: " + sPickupSubtypePerformsProxMatcher.mSpec;
80        }
81    }
82
83    public boolean getDisplayStateSupported() {
84        return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
85    }
86
87    public int getPulseDuration(boolean pickup) {
88        return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
89    }
90
91    public int getPulseInDuration(boolean pickup) {
92        return pickup
93                ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
94                : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
95    }
96
97    public int getPulseVisibleDuration() {
98        return getInt("doze.pulse.duration.visible", R.integer.doze_pulse_duration_visible);
99    }
100
101    public int getPulseOutDuration() {
102        return getInt("doze.pulse.duration.out", R.integer.doze_pulse_duration_out);
103    }
104
105    public boolean getPulseOnSigMotion() {
106        return getBoolean("doze.pulse.sigmotion", R.bool.doze_pulse_on_significant_motion);
107    }
108
109    public boolean getVibrateOnSigMotion() {
110        return SystemProperties.getBoolean("doze.vibrate.sigmotion", false);
111    }
112
113    public boolean getPulseOnPickup() {
114        return getBoolean("doze.pulse.pickup", R.bool.doze_pulse_on_pick_up);
115    }
116
117    public boolean getVibrateOnPickup() {
118        return SystemProperties.getBoolean("doze.vibrate.pickup", false);
119    }
120
121    public boolean getProxCheckBeforePulse() {
122        return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
123    }
124
125    public boolean getPulseOnNotifications() {
126        return getBoolean("doze.pulse.notifications", R.bool.doze_pulse_on_notifications);
127    }
128
129    public PulseSchedule getPulseSchedule() {
130        final String spec = getString("doze.pulse.schedule", R.string.doze_pulse_schedule);
131        if (sPulseSchedule == null || !sPulseSchedule.mSpec.equals(spec)) {
132            sPulseSchedule = PulseSchedule.parse(spec);
133        }
134        return sPulseSchedule;
135    }
136
137    public int getPulseScheduleResets() {
138        return getInt("doze.pulse.schedule.resets", R.integer.doze_pulse_schedule_resets);
139    }
140
141    public int getPickupVibrationThreshold() {
142        return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
143    }
144
145    private boolean getBoolean(String propName, int resId) {
146        return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
147    }
148
149    private int getInt(String propName, int resId) {
150        int value = SystemProperties.getInt(propName, mContext.getResources().getInteger(resId));
151        return MathUtils.constrain(value, 0, MAX_DURATION);
152    }
153
154    private String getString(String propName, int resId) {
155        return SystemProperties.get(propName, mContext.getString(resId));
156    }
157
158    public boolean getPickupSubtypePerformsProxCheck(int subType) {
159        String spec = getString("doze.pickup.proxcheck",
160                R.string.doze_pickup_subtype_performs_proximity_check);
161
162        if (TextUtils.isEmpty(spec)) {
163            // Fall back to non-subtype based property.
164            return mContext.getResources().getBoolean(R.bool.doze_pickup_performs_proximity_check);
165        }
166
167        if (sPickupSubtypePerformsProxMatcher == null
168                || !TextUtils.equals(spec, sPickupSubtypePerformsProxMatcher.mSpec)) {
169            sPickupSubtypePerformsProxMatcher = new IntInOutMatcher(spec);
170        }
171
172        return sPickupSubtypePerformsProxMatcher.isIn(subType);
173    }
174
175
176    /**
177     * Parses a spec of the form `1,2,3,!5,*`. The resulting object will match numbers that are
178     * listed, will not match numbers that are listed with a ! prefix, and will match / not match
179     * unlisted numbers depending on whether * or !* is present.
180     *
181     * *  -> match any numbers that are not explicitly listed
182     * !* -> don't match any numbers that are not explicitly listed
183     * 2  -> match 2
184     * !3 -> don't match 3
185     *
186     * It is illegal to specify:
187     * - an empty spec
188     * - a spec containing that are empty, or a lone !
189     * - a spec for anything other than numbers or *
190     * - multiple terms for the same number / multiple *s
191     */
192    public static class IntInOutMatcher {
193        private static final String WILDCARD = "*";
194        private static final char OUT_PREFIX = '!';
195
196        private final SparseBooleanArray mIsIn;
197        private final boolean mDefaultIsIn;
198        final String mSpec;
199
200        public IntInOutMatcher(String spec) {
201            if (TextUtils.isEmpty(spec)) {
202                throw new IllegalArgumentException("Spec must not be empty");
203            }
204
205            boolean defaultIsIn = false;
206            boolean foundWildcard = false;
207
208            mSpec = spec;
209            mIsIn = new SparseBooleanArray();
210
211            for (String itemPrefixed : spec.split(",", -1)) {
212                if (itemPrefixed.length() == 0) {
213                    throw new IllegalArgumentException(
214                            "Illegal spec, must not have zero-length items: `" + spec + "`");
215                }
216                boolean isIn = itemPrefixed.charAt(0) != OUT_PREFIX;
217                String item = isIn ? itemPrefixed : itemPrefixed.substring(1);
218
219                if (itemPrefixed.length() == 0) {
220                    throw new IllegalArgumentException(
221                            "Illegal spec, must not have zero-length items: `" + spec + "`");
222                }
223
224                if (WILDCARD.equals(item)) {
225                    if (foundWildcard) {
226                        throw new IllegalArgumentException("Illegal spec, `" + WILDCARD +
227                                "` must not appear multiple times in `" + spec + "`");
228                    }
229                    defaultIsIn = isIn;
230                    foundWildcard = true;
231                } else {
232                    int key = Integer.parseInt(item);
233                    if (mIsIn.indexOfKey(key) >= 0) {
234                        throw new IllegalArgumentException("Illegal spec, `" + key +
235                                "` must not appear multiple times in `" + spec + "`");
236                    }
237                    mIsIn.put(key, isIn);
238                }
239            }
240
241            if (!foundWildcard) {
242                throw new IllegalArgumentException("Illegal spec, must specify either * or !*");
243            }
244
245            mDefaultIsIn = defaultIsIn;
246        }
247
248        public boolean isIn(int value) {
249            return (mIsIn.get(value, mDefaultIsIn));
250        }
251    }
252
253    public static class PulseSchedule {
254        private static final Pattern PATTERN = Pattern.compile("(\\d+?)s", 0);
255
256        private String mSpec;
257        private int[] mSchedule;
258
259        public static PulseSchedule parse(String spec) {
260            if (TextUtils.isEmpty(spec)) return null;
261            try {
262                final PulseSchedule rt = new PulseSchedule();
263                rt.mSpec = spec;
264                final String[] tokens = spec.split(",");
265                rt.mSchedule = new int[tokens.length];
266                for (int i = 0; i < tokens.length; i++) {
267                    final Matcher m = PATTERN.matcher(tokens[i]);
268                    if (!m.matches()) throw new IllegalArgumentException("Bad token: " + tokens[i]);
269                    rt.mSchedule[i] = Integer.parseInt(m.group(1));
270                }
271                if (DEBUG) Log.d(TAG, "Parsed spec [" + spec + "] as: " + rt);
272                return rt;
273            } catch (RuntimeException e) {
274                Log.w(TAG, "Error parsing spec: " + spec, e);
275                return null;
276            }
277        }
278
279        @Override
280        public String toString() {
281            return Arrays.toString(mSchedule);
282        }
283
284        public long getNextTime(long now, long notificationTime) {
285            for (int i = 0; i < mSchedule.length; i++) {
286                final long time = notificationTime + mSchedule[i] * 1000;
287                if (time > now) return time;
288            }
289            return 0;
290        }
291    }
292}
293