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