1/*
2 * Copyright (C) 2013 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.settings;
18
19import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
20import static com.android.settingslib.display.BrightnessUtils.convertGammaToLinear;
21import static com.android.settingslib.display.BrightnessUtils.convertLinearToGamma;
22
23import android.animation.ValueAnimator;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.database.ContentObserver;
27import android.hardware.display.DisplayManager;
28import android.net.Uri;
29import android.os.AsyncTask;
30import android.os.Handler;
31import android.os.Looper;
32import android.os.Message;
33import android.os.PowerManager;
34import android.os.RemoteException;
35import android.os.ServiceManager;
36import android.os.UserHandle;
37import android.os.UserManager;
38import android.provider.Settings;
39import android.service.vr.IVrManager;
40import android.service.vr.IVrStateCallbacks;
41import android.util.Log;
42import android.widget.ImageView;
43
44import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
46import com.android.settingslib.RestrictedLockUtils;
47import com.android.systemui.Dependency;
48
49import java.util.ArrayList;
50
51public class BrightnessController implements ToggleSlider.Listener {
52    private static final String TAG = "StatusBar.BrightnessController";
53    private static final boolean SHOW_AUTOMATIC_ICON = false;
54
55    private static final int SLIDER_ANIMATION_DURATION = 3000;
56
57    private static final int MSG_UPDATE_ICON = 0;
58    private static final int MSG_UPDATE_SLIDER = 1;
59    private static final int MSG_SET_CHECKED = 2;
60    private static final int MSG_ATTACH_LISTENER = 3;
61    private static final int MSG_DETACH_LISTENER = 4;
62    private static final int MSG_VR_MODE_CHANGED = 5;
63
64    private final int mMinimumBacklight;
65    private final int mMaximumBacklight;
66    private final int mDefaultBacklight;
67    private final int mMinimumBacklightForVr;
68    private final int mMaximumBacklightForVr;
69    private final int mDefaultBacklightForVr;
70
71    private final Context mContext;
72    private final ImageView mIcon;
73    private final ToggleSlider mControl;
74    private final boolean mAutomaticAvailable;
75    private final DisplayManager mDisplayManager;
76    private final CurrentUserTracker mUserTracker;
77    private final IVrManager mVrManager;
78
79    private final Handler mBackgroundHandler;
80    private final BrightnessObserver mBrightnessObserver;
81
82    private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
83            new ArrayList<BrightnessStateChangeCallback>();
84
85    private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
86    private volatile boolean mIsVrModeEnabled;
87    private boolean mListening;
88    private boolean mExternalChange;
89    private boolean mControlValueInitialized;
90
91    private ValueAnimator mSliderAnimator;
92
93    public interface BrightnessStateChangeCallback {
94        public void onBrightnessLevelChanged();
95    }
96
97    /** ContentObserver to watch brightness **/
98    private class BrightnessObserver extends ContentObserver {
99
100        private final Uri BRIGHTNESS_MODE_URI =
101                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
102        private final Uri BRIGHTNESS_URI =
103                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
104        private final Uri BRIGHTNESS_FOR_VR_URI =
105                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR);
106
107        public BrightnessObserver(Handler handler) {
108            super(handler);
109        }
110
111        @Override
112        public void onChange(boolean selfChange) {
113            onChange(selfChange, null);
114        }
115
116        @Override
117        public void onChange(boolean selfChange, Uri uri) {
118            if (selfChange) return;
119
120            if (BRIGHTNESS_MODE_URI.equals(uri)) {
121                mBackgroundHandler.post(mUpdateModeRunnable);
122                mBackgroundHandler.post(mUpdateSliderRunnable);
123            } else if (BRIGHTNESS_URI.equals(uri)) {
124                mBackgroundHandler.post(mUpdateSliderRunnable);
125            } else if (BRIGHTNESS_FOR_VR_URI.equals(uri)) {
126                mBackgroundHandler.post(mUpdateSliderRunnable);
127            } else {
128                mBackgroundHandler.post(mUpdateModeRunnable);
129                mBackgroundHandler.post(mUpdateSliderRunnable);
130            }
131            for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
132                cb.onBrightnessLevelChanged();
133            }
134        }
135
136        public void startObserving() {
137            final ContentResolver cr = mContext.getContentResolver();
138            cr.unregisterContentObserver(this);
139            cr.registerContentObserver(
140                    BRIGHTNESS_MODE_URI,
141                    false, this, UserHandle.USER_ALL);
142            cr.registerContentObserver(
143                    BRIGHTNESS_URI,
144                    false, this, UserHandle.USER_ALL);
145            cr.registerContentObserver(
146                    BRIGHTNESS_FOR_VR_URI,
147                    false, this, UserHandle.USER_ALL);
148        }
149
150        public void stopObserving() {
151            final ContentResolver cr = mContext.getContentResolver();
152            cr.unregisterContentObserver(this);
153        }
154
155    }
156
157    private final Runnable mStartListeningRunnable = new Runnable() {
158        @Override
159        public void run() {
160            mBrightnessObserver.startObserving();
161            mUserTracker.startTracking();
162
163            // Update the slider and mode before attaching the listener so we don't
164            // receive the onChanged notifications for the initial values.
165            mUpdateModeRunnable.run();
166            mUpdateSliderRunnable.run();
167
168            mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
169        }
170    };
171
172    private final Runnable mStopListeningRunnable = new Runnable() {
173        @Override
174        public void run() {
175            mBrightnessObserver.stopObserving();
176            mUserTracker.stopTracking();
177
178            mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
179        }
180    };
181
182    /**
183     * Fetch the brightness mode from the system settings and update the icon. Should be called from
184     * background thread.
185     */
186    private final Runnable mUpdateModeRunnable = new Runnable() {
187        @Override
188        public void run() {
189            if (mAutomaticAvailable) {
190                int automatic;
191                automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
192                        Settings.System.SCREEN_BRIGHTNESS_MODE,
193                        Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
194                        UserHandle.USER_CURRENT);
195                mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
196                mHandler.obtainMessage(MSG_UPDATE_ICON, mAutomatic ? 1 : 0).sendToTarget();
197            } else {
198                mHandler.obtainMessage(MSG_SET_CHECKED, 0).sendToTarget();
199                mHandler.obtainMessage(MSG_UPDATE_ICON, 0 /* automatic */).sendToTarget();
200            }
201        }
202    };
203
204    /**
205     * Fetch the brightness from the system settings and update the slider. Should be called from
206     * background thread.
207     */
208    private final Runnable mUpdateSliderRunnable = new Runnable() {
209        @Override
210        public void run() {
211            final int val;
212            final boolean inVrMode = mIsVrModeEnabled;
213            if (inVrMode) {
214                val = Settings.System.getIntForUser(mContext.getContentResolver(),
215                        Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mDefaultBacklightForVr,
216                        UserHandle.USER_CURRENT);
217            } else {
218                val = Settings.System.getIntForUser(mContext.getContentResolver(),
219                        Settings.System.SCREEN_BRIGHTNESS, mDefaultBacklight,
220                        UserHandle.USER_CURRENT);
221            }
222            mHandler.obtainMessage(MSG_UPDATE_SLIDER, val, inVrMode ? 1 : 0).sendToTarget();
223        }
224    };
225
226    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
227        @Override
228        public void onVrStateChanged(boolean enabled) {
229            mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
230                    .sendToTarget();
231        }
232    };
233
234    private final Handler mHandler = new Handler() {
235        @Override
236        public void handleMessage(Message msg) {
237            mExternalChange = true;
238            try {
239                switch (msg.what) {
240                    case MSG_UPDATE_ICON:
241                        updateIcon(msg.arg1 != 0);
242                        break;
243                    case MSG_UPDATE_SLIDER:
244                        updateSlider(msg.arg1, msg.arg2 != 0);
245                        break;
246                    case MSG_SET_CHECKED:
247                        mControl.setChecked(msg.arg1 != 0);
248                        break;
249                    case MSG_ATTACH_LISTENER:
250                        mControl.setOnChangedListener(BrightnessController.this);
251                        break;
252                    case MSG_DETACH_LISTENER:
253                        mControl.setOnChangedListener(null);
254                        break;
255                    case MSG_VR_MODE_CHANGED:
256                        updateVrMode(msg.arg1 != 0);
257                        break;
258                    default:
259                        super.handleMessage(msg);
260                }
261            } finally {
262                mExternalChange = false;
263            }
264        }
265    };
266
267    public BrightnessController(Context context, ImageView icon, ToggleSlider control) {
268        mContext = context;
269        mIcon = icon;
270        mControl = control;
271        mControl.setMax(GAMMA_SPACE_MAX);
272        mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
273        mUserTracker = new CurrentUserTracker(mContext) {
274            @Override
275            public void onUserSwitched(int newUserId) {
276                mBackgroundHandler.post(mUpdateModeRunnable);
277                mBackgroundHandler.post(mUpdateSliderRunnable);
278            }
279        };
280        mBrightnessObserver = new BrightnessObserver(mHandler);
281
282        PowerManager pm = context.getSystemService(PowerManager.class);
283        mMinimumBacklight = pm.getMinimumScreenBrightnessSetting();
284        mMaximumBacklight = pm.getMaximumScreenBrightnessSetting();
285        mDefaultBacklight = pm.getDefaultScreenBrightnessSetting();
286        mMinimumBacklightForVr = pm.getMinimumScreenBrightnessForVrSetting();
287        mMaximumBacklightForVr = pm.getMaximumScreenBrightnessForVrSetting();
288        mDefaultBacklightForVr = pm.getDefaultScreenBrightnessForVrSetting();
289
290        mAutomaticAvailable = context.getResources().getBoolean(
291                com.android.internal.R.bool.config_automatic_brightness_available);
292        mDisplayManager = context.getSystemService(DisplayManager.class);
293        mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
294                Context.VR_SERVICE));
295    }
296
297    public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
298        mChangeCallbacks.add(cb);
299    }
300
301    public boolean removeStateChangedCallback(BrightnessStateChangeCallback cb) {
302        return mChangeCallbacks.remove(cb);
303    }
304
305    @Override
306    public void onInit(ToggleSlider control) {
307        // Do nothing
308    }
309
310    public void registerCallbacks() {
311        if (mListening) {
312            return;
313        }
314
315        if (mVrManager != null) {
316            try {
317                mVrManager.registerListener(mVrStateCallbacks);
318                mIsVrModeEnabled = mVrManager.getVrModeState();
319            } catch (RemoteException e) {
320                Log.e(TAG, "Failed to register VR mode state listener: ", e);
321            }
322        }
323
324        mBackgroundHandler.post(mStartListeningRunnable);
325        mListening = true;
326    }
327
328    /** Unregister all call backs, both to and from the controller */
329    public void unregisterCallbacks() {
330        if (!mListening) {
331            return;
332        }
333
334        if (mVrManager != null) {
335            try {
336                mVrManager.unregisterListener(mVrStateCallbacks);
337            } catch (RemoteException e) {
338                Log.e(TAG, "Failed to unregister VR mode state listener: ", e);
339            }
340        }
341
342        mBackgroundHandler.post(mStopListeningRunnable);
343        mListening = false;
344        mControlValueInitialized = false;
345    }
346
347    @Override
348    public void onChanged(ToggleSlider toggleSlider, boolean tracking, boolean automatic,
349            int value, boolean stopTracking) {
350        updateIcon(mAutomatic);
351        if (mExternalChange) return;
352
353        if (mSliderAnimator != null) {
354            mSliderAnimator.cancel();
355        }
356
357        final int min;
358        final int max;
359        final int metric;
360        final String setting;
361
362        if (mIsVrModeEnabled) {
363            metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;
364            min = mMinimumBacklightForVr;
365            max = mMaximumBacklightForVr;
366            setting = Settings.System.SCREEN_BRIGHTNESS_FOR_VR;
367        } else {
368            metric = mAutomatic
369                    ? MetricsEvent.ACTION_BRIGHTNESS_AUTO
370                    : MetricsEvent.ACTION_BRIGHTNESS;
371            min = mMinimumBacklight;
372            max = mMaximumBacklight;
373            setting = Settings.System.SCREEN_BRIGHTNESS;
374        }
375
376        final int val = convertGammaToLinear(value, min, max);
377
378        if (stopTracking) {
379            MetricsLogger.action(mContext, metric, val);
380        }
381
382        setBrightness(val);
383        if (!tracking) {
384            AsyncTask.execute(new Runnable() {
385                    public void run() {
386                        Settings.System.putIntForUser(mContext.getContentResolver(),
387                                setting, val, UserHandle.USER_CURRENT);
388                    }
389                });
390        }
391
392        for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
393            cb.onBrightnessLevelChanged();
394        }
395    }
396
397    public void checkRestrictionAndSetEnabled() {
398        mBackgroundHandler.post(new Runnable() {
399            @Override
400            public void run() {
401                ((ToggleSliderView)mControl).setEnforcedAdmin(
402                        RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
403                                UserManager.DISALLOW_CONFIG_BRIGHTNESS,
404                                mUserTracker.getCurrentUserId()));
405            }
406        });
407    }
408
409    private void setMode(int mode) {
410        Settings.System.putIntForUser(mContext.getContentResolver(),
411                Settings.System.SCREEN_BRIGHTNESS_MODE, mode,
412                mUserTracker.getCurrentUserId());
413    }
414
415    private void setBrightness(int brightness) {
416        mDisplayManager.setTemporaryBrightness(brightness);
417    }
418
419    private void setBrightnessAdj(float adj) {
420        mDisplayManager.setTemporaryAutoBrightnessAdjustment(adj);
421    }
422
423    private void updateIcon(boolean automatic) {
424        if (mIcon != null) {
425            mIcon.setImageResource(automatic && SHOW_AUTOMATIC_ICON ?
426                    com.android.systemui.R.drawable.ic_qs_brightness_auto_on :
427                    com.android.systemui.R.drawable.ic_qs_brightness_auto_off);
428        }
429    }
430
431    private void updateVrMode(boolean isEnabled) {
432        if (mIsVrModeEnabled != isEnabled) {
433            mIsVrModeEnabled = isEnabled;
434            mBackgroundHandler.post(mUpdateSliderRunnable);
435        }
436    }
437
438    private void updateSlider(int val, boolean inVrMode) {
439        final int min;
440        final int max;
441        if (inVrMode) {
442            min = mMinimumBacklightForVr;
443            max = mMaximumBacklightForVr;
444        } else {
445            min = mMinimumBacklight;
446            max = mMaximumBacklight;
447        }
448        if (val == convertGammaToLinear(mControl.getValue(), min, max)) {
449            // If we have more resolution on the slider than we do in the actual setting, then
450            // multiple slider positions will map to the same setting value. Thus, if we see a
451            // setting value here that maps to the current slider position, we don't bother to
452            // calculate the new slider position since it may differ and look like a brightness
453            // change to the user even though it isn't one.
454            return;
455        }
456        final int sliderVal = convertLinearToGamma(val, min, max);
457        animateSliderTo(sliderVal);
458    }
459
460    private void animateSliderTo(int target) {
461        if (!mControlValueInitialized) {
462            // Don't animate the first value since it's default state isn't meaningful to users.
463            mControl.setValue(target);
464            mControlValueInitialized = true;
465        }
466        if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
467            mSliderAnimator.cancel();
468        }
469        mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
470        mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
471            mExternalChange = true;
472            mControl.setValue((int)animation.getAnimatedValue());
473            mExternalChange = false;
474        });
475        mSliderAnimator.setDuration(SLIDER_ANIMATION_DURATION);
476        mSliderAnimator.start();
477    }
478
479}
480