1 /*
2  * Copyright (C) 2021 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 package com.android.customization.model.mode;
17 
18 
19 import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE;
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
22 
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.PowerManager;
30 import android.text.TextUtils;
31 import android.view.LayoutInflater;
32 import android.widget.Switch;
33 import android.widget.Toast;
34 
35 import androidx.annotation.MainThread;
36 import androidx.core.content.ContextCompat;
37 import androidx.lifecycle.Lifecycle;
38 import androidx.lifecycle.LifecycleObserver;
39 import androidx.lifecycle.OnLifecycleEvent;
40 
41 import com.android.customization.module.logging.ThemesUserEventLogger;
42 import com.android.customization.picker.mode.DarkModeSectionView;
43 import com.android.themepicker.R;
44 import com.android.wallpaper.model.CustomizationSectionController;
45 import com.android.wallpaper.system.UiModeManagerWrapper;
46 
47 import java.util.concurrent.ExecutorService;
48 import java.util.concurrent.Executors;
49 
50 /** Section for dark theme toggle that controls if this section will be shown visually. */
51 public class DarkModeSectionController implements
52         CustomizationSectionController<DarkModeSectionView>, LifecycleObserver {
53 
54     private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor();
55 
56     private final Lifecycle mLifecycle;
57     private final PowerManager mPowerManager;
58     private final BatterySaverStateReceiver mBatterySaverStateReceiver =
59             new BatterySaverStateReceiver();
60 
61     private Context mContext;
62     private DarkModeSectionView mDarkModeSectionView;
63     private final DarkModeSnapshotRestorer mSnapshotRestorer;
64     private final UiModeManagerWrapper mUiModeManager;
65     private final ThemesUserEventLogger mThemesUserEventLogger;
66 
DarkModeSectionController( Context context, Lifecycle lifecycle, DarkModeSnapshotRestorer snapshotRestorer, UiModeManagerWrapper uiModeManager, ThemesUserEventLogger themesUserEventLogger)67     public DarkModeSectionController(
68             Context context,
69             Lifecycle lifecycle,
70             DarkModeSnapshotRestorer snapshotRestorer,
71             UiModeManagerWrapper uiModeManager,
72             ThemesUserEventLogger themesUserEventLogger) {
73         mContext = context;
74         mLifecycle = lifecycle;
75         mPowerManager = context.getSystemService(PowerManager.class);
76         mLifecycle.addObserver(this);
77         mSnapshotRestorer = snapshotRestorer;
78         mUiModeManager = uiModeManager;
79         mThemesUserEventLogger = themesUserEventLogger;
80     }
81 
82     @OnLifecycleEvent(Lifecycle.Event.ON_START)
83     @MainThread
onStart()84     public void onStart() {
85         sExecutorService.submit(() -> {
86             if (mContext != null && mLifecycle.getCurrentState().isAtLeast(
87                     Lifecycle.State.STARTED)) {
88                 mContext.registerReceiver(mBatterySaverStateReceiver,
89                         new IntentFilter(ACTION_POWER_SAVE_MODE_CHANGED));
90             }
91         });
92     }
93 
94     @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
95     @MainThread
onStop()96     public void onStop() {
97         sExecutorService.submit(() -> {
98             if (mContext != null && mBatterySaverStateReceiver != null) {
99                 mContext.unregisterReceiver(mBatterySaverStateReceiver);
100             }
101         });
102     }
103 
104     @Override
release()105     public void release() {
106         mLifecycle.removeObserver(this);
107         mContext = null;
108     }
109 
110     @Override
isAvailable(Context context)111     public boolean isAvailable(Context context) {
112         if (context == null) {
113             return false;
114         }
115         return ContextCompat.checkSelfPermission(context, MODIFY_DAY_NIGHT_MODE)
116                 == PERMISSION_GRANTED;
117     }
118 
119     @Override
createView(Context context)120     public DarkModeSectionView createView(Context context) {
121         mDarkModeSectionView = (DarkModeSectionView) LayoutInflater.from(
122                 context).inflate(R.layout.dark_mode_section_view, /* root= */ null);
123         mDarkModeSectionView.setViewListener(this::onViewActivated);
124         PowerManager pm = context.getSystemService(PowerManager.class);
125         mDarkModeSectionView.setEnabled(!pm.isPowerSaveMode());
126         return mDarkModeSectionView;
127     }
128 
onViewActivated(Context context, boolean viewActivated)129     private void onViewActivated(Context context, boolean viewActivated) {
130         if (context == null) {
131             return;
132         }
133         Switch switchView = mDarkModeSectionView.findViewById(R.id.dark_mode_toggle);
134         if (!switchView.isEnabled()) {
135             Toast disableToast = Toast.makeText(mContext,
136                     mContext.getString(R.string.mode_disabled_msg), Toast.LENGTH_SHORT);
137             disableToast.show();
138             return;
139         }
140         int shortDelay = context.getResources().getInteger(android.R.integer.config_shortAnimTime);
141         new Handler(Looper.getMainLooper()).postDelayed(
142                 () -> {
143                     mDarkModeSectionView.announceForAccessibility(
144                             context.getString(R.string.mode_changed));
145                     mUiModeManager.setNightModeActivated(viewActivated);
146                     mThemesUserEventLogger.logDarkThemeApplied(viewActivated);
147                     mSnapshotRestorer.store(viewActivated);
148                 },
149                 /* delayMillis= */ shortDelay);
150     }
151 
152     private class BatterySaverStateReceiver extends BroadcastReceiver {
153         @Override
onReceive(Context context, Intent intent)154         public void onReceive(Context context, Intent intent) {
155             if (TextUtils.equals(intent.getAction(), ACTION_POWER_SAVE_MODE_CHANGED)
156                     && mDarkModeSectionView != null) {
157                 mDarkModeSectionView.setEnabled(!mPowerManager.isPowerSaveMode());
158             }
159         }
160     }
161 }
162