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 DarkModeSectionController(Context context, Lifecycle lifecycle)63 public DarkModeSectionController(Context context, Lifecycle lifecycle) { 64 mContext = context; 65 mLifecycle = lifecycle; 66 mPowerManager = context.getSystemService(PowerManager.class); 67 mLifecycle.addObserver(this); 68 } 69 70 @OnLifecycleEvent(Lifecycle.Event.ON_START) 71 @MainThread onStart()72 public void onStart() { 73 sExecutorService.submit(() -> { 74 if (mContext != null && mLifecycle.getCurrentState().isAtLeast( 75 Lifecycle.State.STARTED)) { 76 mContext.registerReceiver(mBatterySaverStateReceiver, 77 new IntentFilter(ACTION_POWER_SAVE_MODE_CHANGED)); 78 } 79 }); 80 } 81 82 @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 83 @MainThread onStop()84 public void onStop() { 85 sExecutorService.submit(() -> { 86 if (mContext != null && mBatterySaverStateReceiver != null) { 87 mContext.unregisterReceiver(mBatterySaverStateReceiver); 88 } 89 }); 90 } 91 92 @Override release()93 public void release() { 94 mLifecycle.removeObserver(this); 95 mContext = null; 96 } 97 98 @Override isAvailable(Context context)99 public boolean isAvailable(Context context) { 100 if (context == null) { 101 return false; 102 } 103 return ContextCompat.checkSelfPermission(context, MODIFY_DAY_NIGHT_MODE) 104 == PERMISSION_GRANTED; 105 } 106 107 @Override createView(Context context)108 public DarkModeSectionView createView(Context context) { 109 mDarkModeSectionView = (DarkModeSectionView) LayoutInflater.from( 110 context).inflate(R.layout.dark_mode_section_view, /* root= */ null); 111 mDarkModeSectionView.setViewListener(this::onViewActivated); 112 PowerManager pm = context.getSystemService(PowerManager.class); 113 mDarkModeSectionView.setEnabled(!pm.isPowerSaveMode()); 114 return mDarkModeSectionView; 115 } 116 onViewActivated(Context context, boolean viewActivated)117 private void onViewActivated(Context context, boolean viewActivated) { 118 if (context == null) { 119 return; 120 } 121 Switch switchView = mDarkModeSectionView.findViewById(R.id.dark_mode_toggle); 122 if (!switchView.isEnabled()) { 123 Toast disableToast = Toast.makeText(mContext, 124 mContext.getString(R.string.mode_disabled_msg), Toast.LENGTH_SHORT); 125 disableToast.show(); 126 return; 127 } 128 UiModeManager uiModeManager = context.getSystemService(UiModeManager.class); 129 int shortDelay = context.getResources().getInteger(android.R.integer.config_shortAnimTime); 130 new Handler(Looper.getMainLooper()).postDelayed( 131 () -> { 132 mDarkModeSectionView.announceForAccessibility( 133 context.getString(R.string.mode_changed)); 134 uiModeManager.setNightModeActivated(viewActivated); 135 }, 136 /* delayMillis= */ shortDelay); 137 } 138 139 private class BatterySaverStateReceiver extends BroadcastReceiver { 140 @Override onReceive(Context context, Intent intent)141 public void onReceive(Context context, Intent intent) { 142 if (TextUtils.equals(intent.getAction(), ACTION_POWER_SAVE_MODE_CHANGED) 143 && mDarkModeSectionView != null) { 144 mDarkModeSectionView.setEnabled(!mPowerManager.isPowerSaveMode()); 145 } 146 } 147 } 148 } 149