ThemePicker: First iteration of Grid section
Create the Fragment and provider to interface with
Launcher.
Some polish is still pending, as well as applying the grid
to the launcher.
Bug: 120560197
Change-Id: I5fc35c901a33f2163d2864bfc9e177c74d49aa36
diff --git a/res/layout/fragment_grid_picker.xml b/res/layout/fragment_grid_picker.xml
new file mode 100644
index 0000000..536f1f7
--- /dev/null
+++ b/res/layout/fragment_grid_picker.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@color/category_picker_background_color">
+ <include layout="@layout/section_header"/>
+
+ <com.android.customization.widget.PreviewPager
+ android:id="@+id/grid_preview_pager"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:background="@color/secondary_color"/>
+
+ <LinearLayout
+ android:id="@+id/options_section"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:orientation="vertical">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/options_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/options_container_height"
+ android:layout_gravity="center_horizontal"/>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <Button
+ style="@style/ActionPrimaryButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:text="@string/apply_btn"/>
+ </RelativeLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/grid_option.xml b/res/layout/grid_option.xml
new file mode 100644
index 0000000..0dac4fe
--- /dev/null
+++ b/res/layout/grid_option.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/option_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/theme_option_label_margin"
+ android:textAppearance="@style/OptionTitleTextAppearance"/>
+ <FrameLayout
+ android:id="@+id/option_tile"
+ android:layout_width="@dimen/option_tile_width"
+ android:layout_height="@dimen/option_tile_width"
+ android:layout_gravity="center_horizontal"
+ android:padding="@dimen/option_tile_padding_vertical"
+ android:background="@drawable/option_border"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/grid_option_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/grid_preview_card.xml b/res/layout/grid_preview_card.xml
new file mode 100644
index 0000000..ae66b83
--- /dev/null
+++ b/res/layout/grid_preview_card.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<androidx.cardview.widget.CardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/FullContentPreviewCard"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/grid_preview_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/primary_color"/>
+
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/res/menu/bottom_navigation_menu.xml b/res/menu/bottom_navigation_menu.xml
index a856222..d477aaf 100644
--- a/res/menu/bottom_navigation_menu.xml
+++ b/res/menu/bottom_navigation_menu.xml
@@ -23,11 +23,11 @@
<item
android:id="@+id/nav_clock"
android:title="@string/clock_title"
- android:icon="@drawable/ic_nav_grid" />
+ android:icon="@drawable/ic_nav_clock" />
<item
android:id="@+id/nav_grid"
android:title="@string/grid_title"
- android:icon="@drawable/ic_nav_theme" />
+ android:icon="@drawable/ic_nav_grid" />
<item
android:id="@+id/nav_wallpaper"
android:title="@string/wallpaper_title"
diff --git a/res/values/override.xml b/res/values/override.xml
index b40eec8..82fc964 100644
--- a/res/values/override.xml
+++ b/res/values/override.xml
@@ -17,4 +17,6 @@
-->
<resources>
<string name="themes_stub_package" translatable="false"/>
+
+ <string name="grid_control_authority" translatable="false"/>
</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a09afaa..d87a354 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -38,6 +38,10 @@
[CHAR LIMIT=20] -->
<string name="apply_theme_btn">Apply Theme</string>
+ <!-- Label for a button that allows the user to apply the currently selected customization option.
+ [CHAR LIMIT=20] -->
+ <string name="apply_btn">Apply</string>
+
<!-- Accessibility label for paging indicator in theme picker preview [CHAR LIMIT=NONE] -->
<string name="accessibility_preview_pager">Page <xliff:g name="current_page" example="1">%1$d</xliff:g> of <xliff:g name="num_pages" example="2">%2$d</xliff:g></string>
<!-- Content description of the next button to bring user to the next preview page.[CHAR LIMIT=NONE] -->
@@ -58,4 +62,7 @@
<!-- Body text for previewing a font [CHAR LIMIT=160] -->
<string name="font_card_body">My Style on every screen!\nOnce you install a theme, its design extends to every facet of the phone, from the home screen to icons, Quick Settings, etc.</string>
+
+ <!--Title for a grid option, describing the number of columns and rows, eg: 4x4 [CHAR LIMIT=10] -->
+ <string name="grid_title_pattern"><xliff:g name="num_cols" example="1">%1$d</xliff:g>x<xliff:g name="num_rows" example="1">%2$d</xliff:g></string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1e46cad..4e5103c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -63,6 +63,13 @@
<item name="android:background">@color/primary_color</item>
</style>
+ <style name="FullContentPreviewCard" parent="PreviewCard">
+ <item name="cardCornerRadius">@dimen/preview_card_corner_radius</item>
+ <item name="android:clipChildren">true</item>
+ <item name="contentPadding">0dp</item>
+ <item name="android:background">@color/primary_color</item>
+ </style>
+
<style name="CardTitleTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
<item name="android:textStyle">bold</item>
<item name="android:textSize">@dimen/card_title_text_size</item>
diff --git a/src/com/android/customization/model/ResourceConstants.java b/src/com/android/customization/model/ResourceConstants.java
new file mode 100644
index 0000000..3158e60
--- /dev/null
+++ b/src/com/android/customization/model/ResourceConstants.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * Holds common strings used to reference system resources.
+ */
+public interface ResourceConstants {
+
+ /**
+ * Package name for android platform resources.
+ */
+ String ANDROID_PACKAGE = "android";
+
+ /**
+ * Name of the system resource for icon mask
+ */
+ String CONFIG_ICON_MASK = "config_icon_mask";
+
+}
diff --git a/src/com/android/customization/model/grid/GridOption.java b/src/com/android/customization/model/grid/GridOption.java
new file mode 100644
index 0000000..1bc34d0
--- /dev/null
+++ b/src/com/android/customization/model/grid/GridOption.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 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.graphics.PorterDuff.Mode;
+import android.net.Uri;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.widget.GridTileDrawable;
+import com.android.wallpaper.R;
+
+/**
+ * Represents a grid layout option available in the current launcher.
+ */
+public class GridOption implements CustomizationOption {
+
+ private final String mTitle;
+ private final boolean mIsCurrent;
+ private final GridTileDrawable mTileDrawable;
+ public final String name;
+ public final int rows;
+ public final int cols;
+ public final Uri previewImageUri;
+ public final int previewPagesCount;
+
+ public GridOption(String title, String name, boolean isCurrent, int rows, int cols,
+ Uri previewImageUri, int previewPagesCount, String iconShapePath) {
+ mTitle = title;
+ mIsCurrent = isCurrent;
+ mTileDrawable = new GridTileDrawable(rows, cols, iconShapePath);
+ this.name = name;
+ this.rows = rows;
+ this.cols = cols;
+ this.previewImageUri = previewImageUri;
+ this.previewPagesCount = previewPagesCount;
+ }
+
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public void bindThumbnailTile(View view) {
+ Context context = view.getContext();
+
+ mTileDrawable.setColorFilter(context.getResources().getColor(
+ R.color.material_grey500, null), Mode.ADD);
+ ((ImageView) view.findViewById(R.id.grid_option_thumbnail))
+ .setImageDrawable(mTileDrawable);
+ }
+
+ @Override
+ public boolean isCurrentlySet() {
+ return mIsCurrent;
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return R.layout.grid_option;
+ }
+}
diff --git a/src/com/android/customization/model/grid/GridOptionsManager.java b/src/com/android/customization/model/grid/GridOptionsManager.java
new file mode 100644
index 0000000..365a822
--- /dev/null
+++ b/src/com/android/customization/model/grid/GridOptionsManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.os.AsyncTask;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager;
+
+import java.util.List;
+
+/**
+ * {@link CustomizationManager} for interfacing with the launcher to handle {@link GridOption}s.
+ */
+public class GridOptionsManager implements CustomizationManager<GridOption> {
+
+ private final LauncherGridOptionsProvider mProvider;
+
+ public GridOptionsManager(LauncherGridOptionsProvider provider) {
+ mProvider = provider;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mProvider.areGridsAvailable();
+ }
+
+ @Override
+ public void apply(GridOption option) {
+ mProvider.applyGrid(option.name);
+ }
+
+ @Override
+ public void fetchOptions(OptionsFetchedListener<GridOption> callback) {
+ new FetchTask(mProvider, callback).execute();
+ }
+
+ private static class FetchTask extends AsyncTask<Void, Void, List<GridOption>> {
+ private final LauncherGridOptionsProvider mProvider;
+ @Nullable private final OptionsFetchedListener<GridOption> mCallback;
+
+ private FetchTask(@NonNull LauncherGridOptionsProvider provider,
+ @Nullable OptionsFetchedListener<GridOption> callback) {
+ mCallback = callback;
+ mProvider = provider;
+ }
+
+ @Override
+ protected List<GridOption> doInBackground(Void[] params) {
+ return mProvider.fetch(false);
+ }
+
+ @Override
+ protected void onPostExecute(List<GridOption> gridOptions) {
+ if (mCallback != null) {
+ mCallback.onOptionsLoaded(gridOptions);
+ }
+ }
+ }
+}
diff --git a/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
new file mode 100644
index 0000000..d43cdc7
--- /dev/null
+++ b/src/com/android/customization/model/grid/LauncherGridOptionsProvider.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 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.ContentResolver;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import androidx.annotation.WorkerThread;
+
+import com.android.customization.model.ResourceConstants;
+import com.android.wallpaper.R;
+
+import com.bumptech.glide.Glide;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstracts the logic to retrieve available grid options from the current Launcher.
+ */
+public class LauncherGridOptionsProvider {
+
+ private static final String LIST_OPTIONS = "list_options";
+ private static final String PREVIEW = "preview";
+
+ private static final String COL_NAME = "name";
+ private static final String COL_ROWS = "rows";
+ private static final String COL_COLS = "cols";
+ private static final String COL_PREVIEW_COUNT = "preview_count";
+ private static final String COL_IS_DEFAULT = "is_default";
+
+ private final Context mContext;
+ private final String mGridProviderAuthority;
+ private final ProviderInfo mProviderInfo;
+ private List<GridOption> mOptions;
+
+ public LauncherGridOptionsProvider(Context context) {
+ mContext = context;
+ // TODO: read this from Activity metadata instead
+ mGridProviderAuthority = mContext.getString(R.string.grid_control_authority);
+ // TODO: check permissions if needed
+ mProviderInfo = TextUtils.isEmpty(mGridProviderAuthority) ? null
+ : mContext.getPackageManager().resolveContentProvider(mGridProviderAuthority, 0);
+ }
+
+ boolean areGridsAvailable() {
+ return mProviderInfo != null;
+ }
+
+ /**
+ * Retrieve the available grids.
+ * @param reload whether to reload grid options if they're cached.
+ */
+ @WorkerThread
+ List<GridOption> fetch(boolean reload) {
+ if (!areGridsAvailable()) {
+ return null;
+ }
+ if (mOptions != null && !reload) {
+ return mOptions;
+ }
+ Uri optionsUri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(mProviderInfo.authority)
+ .appendPath(LIST_OPTIONS)
+ .build();
+ ContentResolver resolver = mContext.getContentResolver();
+ String iconPath = mContext.getResources().getString(Resources.getSystem().getIdentifier(
+ ResourceConstants.CONFIG_ICON_MASK, "string", ResourceConstants.ANDROID_PACKAGE));
+ try (Cursor c = resolver.query(optionsUri, null, null, null, null)) {
+ mOptions = new ArrayList<>();
+ while(c.moveToNext()) {
+ String name = c.getString(c.getColumnIndex(COL_NAME));
+ int rows = c.getInt(c.getColumnIndex(COL_ROWS));
+ int cols = c.getInt(c.getColumnIndex(COL_COLS));
+ int previewCount = c.getInt(c.getColumnIndex(COL_PREVIEW_COUNT));
+ boolean isSet = Boolean.valueOf(c.getString(c.getColumnIndex(COL_IS_DEFAULT)));
+ Uri preview = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(mProviderInfo.authority)
+ .appendPath(PREVIEW)
+ .appendPath(name)
+ .build();
+ String title = mContext.getString(R.string.grid_title_pattern, cols, rows);
+ mOptions.add(new GridOption(title, name, isSet, rows, cols, preview, previewCount,
+ iconPath));
+ }
+ Glide.get(mContext).clearDiskCache();
+ } catch (Exception e) {
+ mOptions = null;
+ }
+ return mOptions;
+ }
+
+ void applyGrid(String name) {
+ //TODO: implement
+ }
+}
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 92c7946..6d67071 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -15,6 +15,9 @@
*/
package com.android.customization.model.theme;
+import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
+import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -56,8 +59,6 @@
private static final String ACCENT_COLOR_DARK_NAME = "accent_device_default_dark";
private static final String CONFIG_BODY_FONT_FAMILY = "config_bodyFontFamily";
private static final String CONFIG_HEADLINE_FONT_FAMILY = "config_headlineFontFamily";
- private static final String CONFIG_ICON_MASK = "config_icon_mask";
- private static final String ANDROID_PACKAGE = "android";
private final Context mContext;
private final String mStubPackageName;
diff --git a/src/com/android/customization/picker/CustomizationPickerActivity.java b/src/com/android/customization/picker/CustomizationPickerActivity.java
index c27cb90..f0db8a1 100644
--- a/src/com/android/customization/picker/CustomizationPickerActivity.java
+++ b/src/com/android/customization/picker/CustomizationPickerActivity.java
@@ -28,6 +28,15 @@
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
+import com.android.customization.model.CustomizationManager;
+import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.grid.GridOption;
+import com.android.customization.model.grid.GridOptionsManager;
+import com.android.customization.model.grid.LauncherGridOptionsProvider;
+import com.android.customization.model.theme.DefaultThemeProvider;
+import com.android.customization.model.theme.ThemeBundle;
+import com.android.customization.model.theme.ThemeManager;
+import com.android.customization.picker.grid.GridFragment;
import com.android.customization.picker.theme.ThemeFragment;
import com.android.wallpaper.R;
import com.android.wallpaper.model.WallpaperInfo;
@@ -103,7 +112,18 @@
}
private void initSections() {
- mSections.put(R.id.nav_theme, new ThemeSection(R.id.nav_theme));
+ //Theme
+ ThemeManager themeManager = new ThemeManager(new DefaultThemeProvider(this));
+ if (themeManager.isAvailable()) {
+ mSections.put(R.id.nav_theme, new ThemeSection(R.id.nav_theme, themeManager));
+ }
+ // TODO: clock
+ //Grid
+ GridOptionsManager gridManager = new GridOptionsManager(
+ new LauncherGridOptionsProvider(this));
+ if (gridManager.isAvailable()) {
+ mSections.put(R.id.nav_grid, new GridSection(R.id.nav_grid, gridManager));
+ }
mSections.put(R.id.nav_wallpaper, new WallpaperSection(R.id.nav_wallpaper));
//TODO (santie): add other sections if supported by the device
}
@@ -190,15 +210,17 @@
* There should be a concrete subclass per available section, providing the corresponding
* Fragment to be displayed when switching to each section.
*/
- static abstract class CustomizationSection {
+ static abstract class CustomizationSection<T extends CustomizationOption> {
/**
* IdRes used to identify this section in the BottomNavigationView menu.
*/
@IdRes final int id;
+ protected final CustomizationManager<T> mCustomizationManager;
- private CustomizationSection(@IdRes int id) {
+ private CustomizationSection(@IdRes int id, CustomizationManager<T> manager) {
this.id = id;
+ this.mCustomizationManager = manager;
}
/**
@@ -216,7 +238,7 @@
private boolean mForceCategoryRefresh;
private WallpaperSection(int id) {
- super(id);
+ super(id, null);
}
@Override
@@ -235,15 +257,39 @@
}
}
- private class ThemeSection extends CustomizationSection {
+ private class ThemeSection extends CustomizationSection<ThemeBundle> {
- private ThemeSection(int id) {
- super(id);
+ private ThemeFragment mFragment;
+
+ private ThemeSection(int id, ThemeManager manager) {
+ super(id, manager);
}
@Override
Fragment getFragment() {
- return ThemeFragment.newInstance(getString(R.string.theme_title));
+ if (mFragment == null) {
+ mFragment = ThemeFragment.newInstance(getString(R.string.theme_title),
+ (ThemeManager) mCustomizationManager);
+ }
+ return mFragment;
+ }
+ }
+
+ private class GridSection extends CustomizationSection<GridOption> {
+
+ private GridFragment mFragment;
+
+ private GridSection(int id, GridOptionsManager manager) {
+ super(id, manager);
+ }
+
+ @Override
+ Fragment getFragment() {
+ if (mFragment == null) {
+ mFragment = GridFragment.newInstance(getString(R.string.grid_title),
+ (GridOptionsManager) mCustomizationManager);
+ }
+ return mFragment;
}
}
}
diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java
new file mode 100644
index 0000000..5864c8e
--- /dev/null
+++ b/src/com/android/customization/picker/grid/GridFragment.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018 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 static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.cardview.widget.CardView;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.android.customization.model.grid.GridOption;
+import com.android.customization.model.grid.GridOptionsManager;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.PreviewPager;
+import com.android.wallpaper.R;
+import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.asset.ContentUriAsset;
+import com.android.wallpaper.picker.ToolbarFragment;
+
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment that contains the UI for selecting and applying a GridOption.
+ */
+public class GridFragment extends ToolbarFragment {
+
+ public static GridFragment newInstance(CharSequence title, GridOptionsManager manager) {
+ GridFragment fragment = new GridFragment();
+ fragment.setManager(manager);
+ fragment.setArguments(ToolbarFragment.createArguments(title));
+ return fragment;
+ }
+
+ private RecyclerView mOptionsContainer;
+ private OptionSelectorController mOptionsController;
+ private GridOptionsManager mGridManager;
+ private GridOption mSelectedOption;
+ private PreviewPager mPreviewPager;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(
+ R.layout.fragment_grid_picker, container, /* attachToRoot */ false);
+ setUpToolbar(view);
+ mPreviewPager = view.findViewById(R.id.grid_preview_pager);
+ mOptionsContainer = view.findViewById(R.id.options_container);
+ setUpOptions();
+
+ return view;
+ }
+
+ private void setManager(GridOptionsManager manager) {
+ mGridManager = manager;
+ }
+
+ private void createAdapter() {
+ mPreviewPager.setAdapter(new GridPreviewAdapter(mSelectedOption));
+ }
+
+ private void setUpOptions() {
+ mGridManager.fetchOptions(options -> {
+ mOptionsController = new OptionSelectorController(mOptionsContainer, options);
+
+ mOptionsController.addListener(selected -> {
+ mSelectedOption = (GridOption) selected;
+ createAdapter();
+ });
+ mOptionsController.initOptions();
+ for (GridOption option : options) {
+ if (option.isCurrentlySet()) {
+ mSelectedOption = option;
+ }
+ }
+ // For development only, as there should always be a grid set.
+ if (mSelectedOption == null) {
+ mSelectedOption = options.get(0);
+ }
+ createAdapter();
+ });
+ }
+
+ /**
+ * Adapter class for mPreviewPager.
+ * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe,
+ * we don't want to just scroll)
+ */
+ private class GridPreviewAdapter extends PagerAdapter {
+
+ private class PreviewPage {
+ final int pageId;
+ final Asset previewAsset;
+ final int cols;
+ final int rows;
+
+ CardView card;
+
+ private PreviewPage(Context context, int id, Uri previewUri, int rows, int cols) {
+ pageId = id;
+ previewAsset = new ContentUriAsset(context, previewUri,
+ RequestOptions.fitCenterTransform());
+ this.rows = rows;
+ this.cols = cols;
+ }
+
+ public void setCard(CardView card) {
+ this.card = card;
+ }
+
+ void bindPreviewContent() {
+ previewAsset.loadDrawable(getActivity(), card.findViewById(R.id.grid_preview_image),
+ card.getContext().getResources().getColor(R.color.primary_color, null));
+ }
+ }
+
+ private final List<PreviewPage> mPages = new ArrayList<>();
+
+ GridPreviewAdapter(GridOption gridOption) {
+ for (int i = 0; i < gridOption.previewPagesCount; i++) {
+ mPages.add(new PreviewPage(getContext(), i,
+ gridOption.previewImageUri.buildUpon().appendPath("" + i).build(),
+ gridOption.rows, gridOption.cols));
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mPages.size();
+ }
+
+ @Override
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+ return view == ((PreviewPage)object).card;
+ }
+
+ @NonNull
+ @Override
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
+ if (ViewCompat.getLayoutDirection(container) == LAYOUT_DIRECTION_RTL) {
+ position = mPages.size() - 1 - position;
+ }
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ CardView card = (CardView) inflater.inflate(R.layout.grid_preview_card,
+ container, false);
+ PreviewPage page = mPages.get(position);
+
+ page.setCard(card);
+ page.bindPreviewContent();
+ if (card.getParent() != null) {
+ container.removeView(card);
+ }
+ container.addView(card);
+ return page;
+ }
+
+ @Override
+ public void destroyItem(@NonNull ViewGroup container, int position,
+ @NonNull Object object) {
+ ((PreviewPage) object).card = null;
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index 2645576..179c08f 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -35,7 +35,6 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
-import com.android.customization.model.theme.DefaultThemeProvider;
import com.android.customization.model.theme.ThemeBundle;
import com.android.customization.model.theme.ThemeManager;
import com.android.customization.widget.OptionSelectorController;
@@ -51,8 +50,9 @@
*/
public class ThemeFragment extends ToolbarFragment {
- public static ThemeFragment newInstance(CharSequence title) {
+ public static ThemeFragment newInstance(CharSequence title, ThemeManager manager) {
ThemeFragment fragment = new ThemeFragment();
+ fragment.setManager(manager);
fragment.setArguments(ToolbarFragment.createArguments(title));
return fragment;
}
@@ -72,13 +72,16 @@
R.layout.fragment_theme_picker, container, /* attachToRoot */ false);
setUpToolbar(view);
mPreviewPager = view.findViewById(R.id.theme_preview_pager);
- mThemeManager = new ThemeManager(new DefaultThemeProvider(getContext()));
mOptionsContainer = view.findViewById(R.id.options_container);
setUpOptions();
return view;
}
+ private void setManager(ThemeManager manager) {
+ mThemeManager = manager;
+ }
+
private void createAdapter() {
mAdapter = new ThemePreviewAdapter(mSelectedTheme);
mPreviewPager.setAdapter(mAdapter);
diff --git a/src/com/android/customization/widget/GridTileDrawable.java b/src/com/android/customization/widget/GridTileDrawable.java
new file mode 100644
index 0000000..c746aaf
--- /dev/null
+++ b/src/com/android/customization/widget/GridTileDrawable.java
@@ -0,0 +1,81 @@
+package com.android.customization.widget;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import androidx.core.graphics.PathParser;
+
+/**
+ * Drawable that draws a grid rows x cols of icon shapes adjusting their size to fit within its
+ * bounds.
+ */
+public class GridTileDrawable extends Drawable {
+
+ // Path is expected configuration in following dimension: [100 x 100]))
+ private static final float PATH_SIZE = 100f;
+ // We want each "icon" using 80% of the available size, so there's 10% padding on each side
+ private static final float ICON_SCALE = .8f;
+ private final int mCols;
+ private final int mRows;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Path mShapePath;
+ private final Path mTransformedPath;
+ private final Matrix mScaleMatrix;
+ private float mCellSize = -1f;
+ private float mSpaceBetweenIcons;
+
+ public GridTileDrawable(int cols, int rows, String path) {
+ mCols = cols;
+ mRows = rows;
+
+ mShapePath = PathParser.createPathFromPathData(path);
+ mTransformedPath = new Path(mShapePath);
+ mScaleMatrix = new Matrix();
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mCellSize = (float) bounds.height() / mRows;
+ mSpaceBetweenIcons = mCellSize * ((1 - ICON_SCALE) / 2);
+
+ float scaleFactor = (mCellSize * ICON_SCALE) / PATH_SIZE;
+ mScaleMatrix.setScale(scaleFactor, scaleFactor);
+ mShapePath.transform(mScaleMatrix, mTransformedPath);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ for (int r = 0; r < mRows; r++) {
+ for (int c = 0; c < mCols; c++) {
+ int saveCount = canvas.save();
+ float x = (c * mCellSize) + mSpaceBetweenIcons;
+ float y = (r * mCellSize) + mSpaceBetweenIcons;
+ canvas.translate(x, y);
+ canvas.drawPath(mTransformedPath, mPaint);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+}