/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.customization.model.mode; import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.Switch; import android.widget.Toast; import androidx.annotation.MainThread; import androidx.core.content.ContextCompat; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.OnLifecycleEvent; import com.android.customization.module.logging.ThemesUserEventLogger; import com.android.customization.picker.mode.DarkModeSectionView; import com.android.themepicker.R; import com.android.wallpaper.model.CustomizationSectionController; import com.android.wallpaper.system.UiModeManagerWrapper; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** Section for dark theme toggle that controls if this section will be shown visually. */ public class DarkModeSectionController implements CustomizationSectionController, LifecycleObserver { private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor(); private final Lifecycle mLifecycle; private final PowerManager mPowerManager; private final BatterySaverStateReceiver mBatterySaverStateReceiver = new BatterySaverStateReceiver(); private Context mContext; private DarkModeSectionView mDarkModeSectionView; private final DarkModeSnapshotRestorer mSnapshotRestorer; private final UiModeManagerWrapper mUiModeManager; private final ThemesUserEventLogger mThemesUserEventLogger; public DarkModeSectionController( Context context, Lifecycle lifecycle, DarkModeSnapshotRestorer snapshotRestorer, UiModeManagerWrapper uiModeManager, ThemesUserEventLogger themesUserEventLogger) { mContext = context; mLifecycle = lifecycle; mPowerManager = context.getSystemService(PowerManager.class); mLifecycle.addObserver(this); mSnapshotRestorer = snapshotRestorer; mUiModeManager = uiModeManager; mThemesUserEventLogger = themesUserEventLogger; } @OnLifecycleEvent(Lifecycle.Event.ON_START) @MainThread public void onStart() { sExecutorService.submit(() -> { if (mContext != null && mLifecycle.getCurrentState().isAtLeast( Lifecycle.State.STARTED)) { mContext.registerReceiver(mBatterySaverStateReceiver, new IntentFilter(ACTION_POWER_SAVE_MODE_CHANGED)); } }); } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) @MainThread public void onStop() { sExecutorService.submit(() -> { if (mContext != null && mBatterySaverStateReceiver != null) { mContext.unregisterReceiver(mBatterySaverStateReceiver); } }); } @Override public void release() { mLifecycle.removeObserver(this); mContext = null; } @Override public boolean isAvailable(Context context) { if (context == null) { return false; } return ContextCompat.checkSelfPermission(context, MODIFY_DAY_NIGHT_MODE) == PERMISSION_GRANTED; } @Override public DarkModeSectionView createView(Context context) { mDarkModeSectionView = (DarkModeSectionView) LayoutInflater.from( context).inflate(R.layout.dark_mode_section_view, /* root= */ null); mDarkModeSectionView.setViewListener(this::onViewActivated); PowerManager pm = context.getSystemService(PowerManager.class); mDarkModeSectionView.setEnabled(!pm.isPowerSaveMode()); return mDarkModeSectionView; } private void onViewActivated(Context context, boolean viewActivated) { if (context == null) { return; } Switch switchView = mDarkModeSectionView.findViewById(R.id.dark_mode_toggle); if (!switchView.isEnabled()) { Toast disableToast = Toast.makeText(mContext, mContext.getString(R.string.mode_disabled_msg), Toast.LENGTH_SHORT); disableToast.show(); return; } int shortDelay = context.getResources().getInteger(android.R.integer.config_shortAnimTime); new Handler(Looper.getMainLooper()).postDelayed( () -> { mDarkModeSectionView.announceForAccessibility( context.getString(R.string.mode_changed)); mUiModeManager.setNightModeActivated(viewActivated); mThemesUserEventLogger.logDarkThemeApplied(viewActivated); mSnapshotRestorer.store(viewActivated); }, /* delayMillis= */ shortDelay); } private class BatterySaverStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (TextUtils.equals(intent.getAction(), ACTION_POWER_SAVE_MODE_CHANGED) && mDarkModeSectionView != null) { mDarkModeSectionView.setEnabled(!mPowerManager.isPowerSaveMode()); } } } }