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