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