diff options
author | Chihhang Chuang <chihhangchuang@google.com> | 2021-06-08 12:53:24 +0800 |
---|---|---|
committer | Chihhang Chuang <chihhangchuang@google.com> | 2021-06-08 13:35:02 +0800 |
commit | c21a3ffb6726945e8d56845eb326a1c835529fc9 (patch) | |
tree | fc5cc8a709965b97b2ef843a9f0a314666899944 /src/com/android | |
parent | 310824f4ab13995a34c735abb6d3acb5f66f2462 (diff) | |
download | ThemePicker-c21a3ffb6726945e8d56845eb326a1c835529fc9.tar.gz |
[1/n] Move new picker logic to AOSP
- Dark/light Theme.
- Themed Icon.
- App Grid.
Bug: 190354625
Test: Build and run the app.
Change-Id: I54c7c3897568019326b827091270091bc114611c
Diffstat (limited to 'src/com/android')
9 files changed, 593 insertions, 0 deletions
diff --git a/src/com/android/customization/model/HubSectionController.java b/src/com/android/customization/model/HubSectionController.java new file mode 100644 index 00000000..765e520a --- /dev/null +++ b/src/com/android/customization/model/HubSectionController.java @@ -0,0 +1,60 @@ +/* + * 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; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.android.customization.picker.SectionView; + +/** + * The interface for the behavior of section in the Customization hub. + * + * @param <T> the {@link SectionView} to create for the section + */ +public interface HubSectionController<T extends SectionView> { + + /** Interface for Customization hub section navigation. */ + interface HubSectionNavigationController { + /** Navigates to the given {@code fragment}. */ + void navigateTo(Fragment fragment); + } + + /** Interface for Customization hub section's dark mode responding to battery saver. */ + interface HubSectionBatterySaverListener { + /** Callback when battery saver's state changed with given {@code isEnabled}. */ + void onBatterySaverStateChanged(boolean isEnabled); + } + + /** Returns {@code true} if the Customization hub section is available. */ + boolean isAvailable(@Nullable Context context); + + /** + * Returns a newly created {@link SectionView} for the section. + * + * @param context the {@link Context} to inflate view + */ + T createView(Context context); + + /** Saves the view state for configuration changes. */ + default void onSaveInstanceState(Bundle savedInstanceState) {} + + /** Releases the controller. */ + default void release() {} +} diff --git a/src/com/android/customization/model/grid/GridSectionController.java b/src/com/android/customization/model/grid/GridSectionController.java new file mode 100644 index 00000000..a84e878f --- /dev/null +++ b/src/com/android/customization/model/grid/GridSectionController.java @@ -0,0 +1,90 @@ +/* + * 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.grid; + +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.android.customization.model.CustomizationManager.OptionsFetchedListener; +import com.android.customization.model.HubSectionController; +import com.android.customization.picker.grid.GridFragment; +import com.android.customization.picker.grid.GridSectionView; +import com.android.wallpaper.R; + +import java.util.List; + +/** A {@link HubSectionController} for app grid. */ +public class GridSectionController implements HubSectionController<GridSectionView> { + + private static final String TAG = "GridSectionController"; + + private final GridOptionsManager mGridOptionsManager; + private final HubSectionNavigationController mHubSectionNavigationController; + + public GridSectionController(GridOptionsManager gridOptionsManager, + HubSectionNavigationController hubSectionNavigationController) { + mGridOptionsManager = gridOptionsManager; + mHubSectionNavigationController = hubSectionNavigationController; + } + + @Override + public boolean isAvailable(Context context) { + return mGridOptionsManager.isAvailable(); + } + + @Override + public GridSectionView createView(Context context) { + GridSectionView gridSectionView = (GridSectionView) LayoutInflater.from(context) + .inflate(R.layout.grid_section_view, /* root= */ null); + TextView sectionDescription = gridSectionView.findViewById(R.id.grid_section_description); + View sectionTile = gridSectionView.findViewById(R.id.grid_section_tile); + + // Fetch grid options to show currently set grid. + mGridOptionsManager.fetchOptions(new OptionsFetchedListener<GridOption>() { + @Override + public void onOptionsLoaded(List<GridOption> options) { + sectionDescription.setText(getActiveOption(options).getTitle()); + } + + @Override + public void onError(@Nullable Throwable throwable) { + if (throwable != null) { + Log.e(TAG, "Error loading grid options", throwable); + } + sectionDescription.setText(R.string.something_went_wrong); + sectionTile.setVisibility(View.GONE); + } + }, /* reload= */ true); + + gridSectionView.setOnClickListener(v -> mHubSectionNavigationController.navigateTo( + GridFragment.newInstance(context.getString(R.string.grid_title)))); + + return gridSectionView; + } + + private GridOption getActiveOption(List<GridOption> options) { + return options.stream() + .filter(option -> option.isActive(mGridOptionsManager)) + .findAny() + // For development only, as there should always be a grid set. + .orElse(options.get(0)); + } +} diff --git a/src/com/android/customization/model/mode/BatterySaverStateReceiver.java b/src/com/android/customization/model/mode/BatterySaverStateReceiver.java new file mode 100644 index 00000000..f3a9c05b --- /dev/null +++ b/src/com/android/customization/model/mode/BatterySaverStateReceiver.java @@ -0,0 +1,47 @@ +/* + * 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.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.text.TextUtils; + +import com.android.customization.model.HubSectionController.HubSectionBatterySaverListener; + +/** + * Broadcast receiver for getting battery saver state and callback to + * {@link HubSectionBatterySaverListener} + */ +public class BatterySaverStateReceiver extends BroadcastReceiver { + + private final HubSectionBatterySaverListener mHubSectionBatterySaverListener; + + public BatterySaverStateReceiver(HubSectionBatterySaverListener batterySaverController) { + mHubSectionBatterySaverListener = batterySaverController; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (TextUtils.equals(intent.getAction(), ACTION_POWER_SAVE_MODE_CHANGED)) { + PowerManager pm = context.getSystemService(PowerManager.class); + mHubSectionBatterySaverListener.onBatterySaverStateChanged(pm.isPowerSaveMode()); + } + } +} diff --git a/src/com/android/customization/model/mode/ModeSection.java b/src/com/android/customization/model/mode/ModeSection.java new file mode 100644 index 00000000..9dd4b65e --- /dev/null +++ b/src/com/android/customization/model/mode/ModeSection.java @@ -0,0 +1,121 @@ +/* + * 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.app.UiModeManager; +import android.content.Context; +import android.content.IntentFilter; +import android.os.PowerManager; +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.model.HubSectionController; +import com.android.customization.model.HubSectionController.HubSectionBatterySaverListener; +import com.android.customization.picker.mode.ModeSectionView; +import com.android.wallpaper.R; + +/** + * Section for dark theme toggle that controls if this section will be shown visually + */ +public class ModeSection implements HubSectionController<ModeSectionView>, LifecycleObserver, + HubSectionBatterySaverListener { + + private Context mContext; + private Lifecycle mLifecycle; + private BatterySaverStateReceiver mBatterySaverStateReceiver; + private ModeSectionView mModeSectionView; + + public ModeSection(Context context, Lifecycle lifecycle) { + mContext = context; + mLifecycle = lifecycle; + mBatterySaverStateReceiver = new BatterySaverStateReceiver(this); + mLifecycle.addObserver(this); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + @MainThread + public void onStart() { + if (mContext != null) { + mContext.registerReceiver(mBatterySaverStateReceiver, + new IntentFilter(ACTION_POWER_SAVE_MODE_CHANGED)); + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + @MainThread + public void onStop() { + 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 ModeSectionView createView(Context context) { + mModeSectionView = (ModeSectionView) LayoutInflater.from( + context).inflate(R.layout.mode_section_view, /* root= */ null); + mModeSectionView.setViewListener(this::onViewActivated); + PowerManager pm = context.getSystemService(PowerManager.class); + mModeSectionView.setEnabled(!pm.isPowerSaveMode()); + return mModeSectionView; + } + + private void onViewActivated(Context context, boolean viewActivated) { + if (context == null) { + return; + } + Switch switchView = mModeSectionView.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; + } + UiModeManager uiModeManager = context.getSystemService(UiModeManager.class); + uiModeManager.setNightModeActivated(viewActivated); + } + + @Override + public void onBatterySaverStateChanged(boolean isEnabled) { + mModeSectionView.setEnabled(!isEnabled); + } +} diff --git a/src/com/android/customization/model/themedicon/ThemedIconSectionController.java b/src/com/android/customization/model/themedicon/ThemedIconSectionController.java new file mode 100644 index 00000000..7b2d06ad --- /dev/null +++ b/src/com/android/customization/model/themedicon/ThemedIconSectionController.java @@ -0,0 +1,62 @@ +/* + * 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.themedicon; + +import android.content.Context; +import android.view.LayoutInflater; + +import androidx.annotation.Nullable; + +import com.android.customization.model.HubSectionController; +import com.android.customization.picker.themedicon.ThemedIconSectionView; +import com.android.wallpaper.R; + +/** The {@link HubSectionController} for themed icon section. */ +public class ThemedIconSectionController implements HubSectionController<ThemedIconSectionView> { + + private final ThemedIconSwitchProvider mThemedIconOptionsProvider; + private final ThemedIconViewModel mThemedIconViewModel; + + public ThemedIconSectionController(ThemedIconSwitchProvider themedIconOptionsProvider, + ThemedIconViewModel themedIconViewModel) { + mThemedIconOptionsProvider = themedIconOptionsProvider; + mThemedIconViewModel = themedIconViewModel; + } + + @Override + public boolean isAvailable(@Nullable Context context) { + return context != null && mThemedIconOptionsProvider.isThemedIconAvailable(); + } + + @Override + public ThemedIconSectionView createView(Context context) { + ThemedIconSectionView themedIconColorSectionView = + (ThemedIconSectionView) LayoutInflater.from(context).inflate( + R.layout.themed_icon_section_view, /* root= */ null); + themedIconColorSectionView.setViewListener(this::onViewActivated); + themedIconColorSectionView.getSwitch() + .setChecked(mThemedIconOptionsProvider.fetchThemedIconEnabled()); + return themedIconColorSectionView; + } + + private void onViewActivated(Context context, boolean viewActivated) { + if (context == null) { + return; + } + mThemedIconOptionsProvider.setThemedIconEnabled(viewActivated); + mThemedIconViewModel.getThemedIconEnabled().setValue(viewActivated); + } +} diff --git a/src/com/android/customization/picker/SectionView.java b/src/com/android/customization/picker/SectionView.java new file mode 100644 index 00000000..c1fd21ac --- /dev/null +++ b/src/com/android/customization/picker/SectionView.java @@ -0,0 +1,54 @@ +/* + * 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.picker; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; + +/** + * The SectionView base for views hosting in the {@link + * com.android.customization.picker.hub.HubFragment}. + */ +public abstract class SectionView extends LinearLayout { + + /** The callback for the section view updates. */ + public interface SectionViewListener { + void onViewActivated(@Nullable Context context, boolean viewActivated); + } + + protected SectionViewListener mSectionViewListener; + private String mTitle; + + public SectionView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public void setTitle(String title) { + mTitle = title; + } + + public String getTitle() { + return mTitle; + } + + /** Sets the listener to the {@code SectionView} instance for reacting the view changes. */ + public void setViewListener(SectionViewListener sectionViewListener) { + mSectionViewListener = sectionViewListener; + } +} diff --git a/src/com/android/customization/picker/grid/GridSectionView.java b/src/com/android/customization/picker/grid/GridSectionView.java new file mode 100644 index 00000000..12f72747 --- /dev/null +++ b/src/com/android/customization/picker/grid/GridSectionView.java @@ -0,0 +1,31 @@ +/* + * 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.picker.grid; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +import com.android.customization.picker.SectionView; + +/** The {@link SectionView} for app grid. */ +public final class GridSectionView extends SectionView { + + public GridSectionView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } +} diff --git a/src/com/android/customization/picker/mode/ModeSectionView.java b/src/com/android/customization/picker/mode/ModeSectionView.java new file mode 100644 index 00000000..353579a9 --- /dev/null +++ b/src/com/android/customization/picker/mode/ModeSectionView.java @@ -0,0 +1,71 @@ +/* + * 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.picker.mode; + +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.widget.Switch; + +import androidx.annotation.Nullable; + +import com.android.customization.picker.SectionView; +import com.android.wallpaper.R; + +/** + * The view of section in the Customization Hub fragment. + */ +public final class ModeSectionView extends SectionView { + + private boolean mIsDarkModeActivated; + + public ModeSectionView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setTitle(context.getString(R.string.mode_title)); + mIsDarkModeActivated = (context.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_YES) != 0; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + Switch switchView = findViewById(R.id.dark_mode_toggle); + switchView.setChecked(mIsDarkModeActivated); + switchView.setOnCheckedChangeListener((buttonView, isChecked) -> + switchView.setChecked(mIsDarkModeActivated) + ); + setOnClickListener(view -> modeToggleClicked()); + } + + private void modeToggleClicked() { + mIsDarkModeActivated = !mIsDarkModeActivated; + viewActivated(mIsDarkModeActivated); + } + + private void viewActivated(boolean isChecked) { + if (mSectionViewListener != null) { + mSectionViewListener.onViewActivated(getContext(), isChecked); + } + } + + @Override + public void setEnabled(boolean enabled) { + final int numOfChildViews = getChildCount(); + for (int i = 0; i < numOfChildViews; i++) { + getChildAt(i).setEnabled(enabled); + } + } +} diff --git a/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java b/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java new file mode 100644 index 00000000..12801065 --- /dev/null +++ b/src/com/android/customization/picker/themedicon/ThemedIconSectionView.java @@ -0,0 +1,57 @@ +/* + * 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.picker.themedicon; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Switch; + +import androidx.annotation.Nullable; + +import com.android.customization.picker.SectionView; +import com.android.wallpaper.R; + +/** + * The {@link SectionView} for themed icon section view + */ +public class ThemedIconSectionView extends SectionView { + + private Switch mSwitchView; + + public ThemedIconSectionView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setTitle(context.getString(R.string.themed_icon_title)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSwitchView = findViewById(R.id.themed_icon_toggle); + setOnClickListener(v -> mSwitchView.toggle()); + mSwitchView.setOnCheckedChangeListener((buttonView, isChecked) -> viewActivated(isChecked)); + } + + /** Gets the switch view. */ + public Switch getSwitch() { + return mSwitchView; + } + + private void viewActivated(boolean isChecked) { + if (mSectionViewListener != null) { + mSectionViewListener.onViewActivated(getContext(), isChecked); + } + } +} |