ThemePicker: Implement the Clock section
Add a Fragment to show and apply available clockfaces.
A few more iterations are needed on the UI but this CL focuses
on the functionality.
Bug: 120560400
Change-Id: I809cb861cf4c03c4b6d84b94e839300dbea200a1
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dae680e..48a3941 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -17,7 +17,7 @@
android:restoreAnyVersion="true"
android:supportsRtl="true">
- <activity android:name="com.android.theme.picker.ThemePickerActivity"
+ <activity android:name="com.android.customization.picker.CustomizationPickerActivity"
android:label="@string/app_name"
android:theme="@style/CustomizationTheme.NoActionBar"
android:resizeableActivity="true">
diff --git a/res/layout/clock_option.xml b/res/layout/clock_option.xml
new file mode 100644
index 0000000..467263d
--- /dev/null
+++ b/res/layout/clock_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:paddingHorizontal="@dimen/option_tile_padding_horizontal"
+ android:paddingVertical="@dimen/option_tile_padding_vertical"
+ android:background="@drawable/option_border">
+ <ImageView
+ android:id="@+id/clock_option_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/clock_preview_card.xml b/res/layout/clock_preview_card.xml
new file mode 100644
index 0000000..63c55e1
--- /dev/null
+++ b/res/layout/clock_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/clock_preview_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/clockface_preview_background"/>
+
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/res/layout/fragment_clock_picker.xml b/res/layout/fragment_clock_picker.xml
new file mode 100644
index 0000000..b61c268
--- /dev/null
+++ b/res/layout/fragment_clock_picker.xml
@@ -0,0 +1,59 @@
+<?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/clock_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
+ android:id="@+id/apply_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/values/colors.xml b/res/values/colors.xml
index bbfb5df..4755c37 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,4 +23,6 @@
<color name="shape_thumbnail_color">#b2b2b2</color>
<color name="icon_thumbnail_color">@android:color/black</color>
+
+ <color name="clockface_preview_background">@android:color/black</color>
</resources>
\ No newline at end of file
diff --git a/res/values/override.xml b/res/values/override.xml
index 82fc964..8d2079c 100644
--- a/res/values/override.xml
+++ b/res/values/override.xml
@@ -17,6 +17,7 @@
-->
<resources>
<string name="themes_stub_package" translatable="false"/>
+ <string name="clocks_stub_package" translatable="false"/>
<string name="grid_control_authority" translatable="false"/>
</resources>
\ No newline at end of file
diff --git a/src/com/android/customization/model/CustomizationOption.java b/src/com/android/customization/model/CustomizationOption.java
index c89d12e..87c8690 100644
--- a/src/com/android/customization/model/CustomizationOption.java
+++ b/src/com/android/customization/model/CustomizationOption.java
@@ -15,6 +15,7 @@
*/
package com.android.customization.model;
+import android.content.Context;
import android.view.View;
import androidx.annotation.LayoutRes;
@@ -43,7 +44,7 @@
/**
* Returns whether this option is the one currently set in the System.
*/
- boolean isCurrentlySet();
+ boolean isActive(Context context);
/**
* Return the id of the layout used to show this option in the UI. It must contain a view with
diff --git a/src/com/android/customization/model/ResourcesApkProvider.java b/src/com/android/customization/model/ResourcesApkProvider.java
new file mode 100644
index 0000000..f770dbc
--- /dev/null
+++ b/src/com/android/customization/model/ResourcesApkProvider.java
@@ -0,0 +1,61 @@
+package com.android.customization.model;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+
+public abstract class ResourcesApkProvider {
+ private static final String TAG = "ResourcesApkProvider";
+
+ protected final Context mContext;
+ protected final String mStubPackageName;
+ protected final Resources mStubApkResources;
+
+ public ResourcesApkProvider(Context context, String stubPackageName) {
+ mContext = context;
+ mStubPackageName = stubPackageName;
+ if (TextUtils.isEmpty(mStubPackageName)) {
+ mStubApkResources = null;
+ } else {
+ Resources apkResources = null;
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo stubAppInfo = pm.getApplicationInfo(
+ mStubPackageName, PackageManager.GET_META_DATA);
+ if (stubAppInfo != null) {
+ apkResources = pm.getResourcesForApplication(stubAppInfo);
+ }
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, String.format("Stub APK for %s not found.", mStubPackageName));
+ } finally {
+ mStubApkResources = apkResources;
+ }
+ }
+ }
+
+ protected String[] getItemsFromStub(String arrayName) {
+ int themesListResId = mStubApkResources.getIdentifier(arrayName, "array", mStubPackageName);
+ return mStubApkResources.getStringArray(themesListResId);
+ }
+
+ protected String getItemStringFromStub(String prefix, String itemName) {
+ int resourceId = mStubApkResources.getIdentifier(String.format("%s%s", prefix, itemName),
+ "string", mStubPackageName);
+ return mStubApkResources.getString(resourceId);
+ }
+
+ protected Drawable getItemDrawableFromStub(String prefix, String itemName) {
+ int resourceId = mStubApkResources.getIdentifier(String.format("%s%s", prefix, itemName),
+ "drawable", mStubPackageName);
+ return mStubApkResources.getDrawable(resourceId, null);
+ }
+
+ public boolean isAvailable() {
+ return mStubApkResources != null;
+ }
+}
diff --git a/src/com/android/customization/model/clock/ClockManager.java b/src/com/android/customization/model/clock/ClockManager.java
new file mode 100644
index 0000000..d516110
--- /dev/null
+++ b/src/com/android/customization/model/clock/ClockManager.java
@@ -0,0 +1,48 @@
+/*
+ * 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.clock;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.customization.model.CustomizationManager;
+
+public class ClockManager implements CustomizationManager<Clockface> {
+
+ private final ClockProvider mClockProvider;
+ private final Context mContext;
+
+ public ClockManager(Context context, ClockProvider provider) {
+ mClockProvider = provider;
+ mContext = context;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mClockProvider.isAvailable();
+ }
+
+ @Override
+ public void apply(Clockface option) {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Clockface.CLOCK_FACE_SETTING, option.getId());
+ }
+
+ @Override
+ public void fetchOptions(OptionsFetchedListener<Clockface> callback) {
+ mClockProvider.fetch(callback, false);
+ }
+}
diff --git a/src/com/android/customization/model/clock/ClockProvider.java b/src/com/android/customization/model/clock/ClockProvider.java
new file mode 100644
index 0000000..d9a6779
--- /dev/null
+++ b/src/com/android/customization/model/clock/ClockProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.clock;
+
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+
+/**
+ * Interface for a class that can retrieve Themes from the system.
+ */
+public interface ClockProvider {
+ /**
+ * Returns whether clockfaces are available in the current setup.
+ */
+ boolean isAvailable();
+
+ /**
+ * Retrieve the available clockfaces.
+ * @param callback called when the clockfaces have been retrieved (or immediately if cached)
+ * @param reload whether to reload clockfaces if they're cached.
+ */
+ void fetch(OptionsFetchedListener<Clockface> callback, boolean reload);
+}
diff --git a/src/com/android/customization/model/clock/Clockface.java b/src/com/android/customization/model/clock/Clockface.java
new file mode 100644
index 0000000..4d81677
--- /dev/null
+++ b/src/com/android/customization/model/clock/Clockface.java
@@ -0,0 +1,106 @@
+/*
+ * 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.clock;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.customization.model.CustomizationOption;
+import com.android.wallpaper.R;
+
+public class Clockface implements CustomizationOption {
+
+ // TODO: use constant from Settings.Secure
+ static final String CLOCK_FACE_SETTING = "lock_screen_custom_clock_face";
+ private final String mTitle;
+ private final String mId;
+ private final Drawable mPreview;
+ private final Drawable mThumbnail;
+
+ private Clockface(String title, String id, Drawable preview, Drawable thumbnail) {
+ mTitle = title;
+ mId = id;
+ mPreview = preview;
+ mThumbnail = thumbnail;
+ }
+
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public void bindThumbnailTile(View view) {
+ ((ImageView) view.findViewById(R.id.clock_option_thumbnail)).setImageDrawable(mThumbnail);
+ }
+
+ @Override
+ public boolean isActive(Context context) {
+ String currentClock = Secure.getString(context.getContentResolver(),
+ Clockface.CLOCK_FACE_SETTING);
+ // Empty clock Id is the default system clock
+ return (TextUtils.isEmpty(currentClock) && TextUtils.isEmpty(mId))
+ || (mId != null && mId.equals(currentClock));
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return R.layout.clock_option;
+ }
+
+ public Drawable getPreviewDrawable() {
+ return mPreview;
+ }
+
+ String getId() {
+ return mId;
+ }
+
+ public static class Builder {
+ private String mTitle;
+ private String mId;
+ private Drawable mPreview;
+ private Drawable mThumbnail;
+
+ public Clockface build() {
+ return new Clockface(mTitle, mId, mPreview, mThumbnail);
+ }
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setPreview(Drawable preview) {
+ mPreview = preview;
+ return this;
+ }
+
+ public Builder setThumbnail(Drawable thumbnail) {
+ mThumbnail = thumbnail;
+ return this;
+ }
+ }
+}
diff --git a/src/com/android/customization/model/clock/ResourcesApkClockProvider.java b/src/com/android/customization/model/clock/ResourcesApkClockProvider.java
new file mode 100644
index 0000000..1533db9
--- /dev/null
+++ b/src/com/android/customization/model/clock/ResourcesApkClockProvider.java
@@ -0,0 +1,76 @@
+/*
+ * 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.clock;
+
+import android.content.Context;
+import android.content.res.Resources.NotFoundException;
+import android.util.Log;
+
+import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.ResourcesApkProvider;
+import com.android.customization.model.clock.Clockface.Builder;
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResourcesApkClockProvider extends ResourcesApkProvider implements ClockProvider {
+
+ private static final String TAG = "ResourcesApkClockProvider";
+
+ private static final String CLOCKS_ARRAY = "clocks";
+ private static final String TITLE_PREFIX = "clock_title_";
+ private static final String ID_PREFIX = "clock_id_";
+ private static final String PREVIEW_PREFIX = "clock_preview_";
+ private static final String THUMBNAIL_PREFIX = "clock_thumbnail_";
+
+ private List<Clockface> mClocks;
+
+ public ResourcesApkClockProvider(Context context){
+ super(context, context.getString(R.string.clocks_stub_package));
+ }
+
+ @Override
+ public void fetch(OptionsFetchedListener<Clockface> callback, boolean reload) {
+ if (mClocks == null || reload) {
+ mClocks = new ArrayList<>();
+ loadAll();
+ }
+
+ if(callback != null) {
+ callback.onOptionsLoaded(mClocks);
+ }
+ }
+
+ private void loadAll() {
+ String[] clockNames = getItemsFromStub(CLOCKS_ARRAY);
+
+ for (String clockName : clockNames) {
+ try {
+ Builder builder = new Builder();
+
+ builder.setTitle(getItemStringFromStub(TITLE_PREFIX, clockName))
+ .setId(getItemStringFromStub(ID_PREFIX, clockName))
+ .setPreview(getItemDrawableFromStub(PREVIEW_PREFIX, clockName))
+ .setThumbnail(getItemDrawableFromStub(THUMBNAIL_PREFIX, clockName));
+
+ mClocks.add(builder.build());
+ } catch (NotFoundException e) {
+ Log.i(TAG, "Resource not found, skipping clock", e);
+ }
+ }
+ }
+}
diff --git a/src/com/android/customization/model/grid/GridOption.java b/src/com/android/customization/model/grid/GridOption.java
index 1bc34d0..879a9c1 100644
--- a/src/com/android/customization/model/grid/GridOption.java
+++ b/src/com/android/customization/model/grid/GridOption.java
@@ -67,7 +67,7 @@
}
@Override
- public boolean isCurrentlySet() {
+ public boolean isActive(Context context) {
return mIsCurrent;
}
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 6d67071..33e4ee4 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -19,8 +19,6 @@
import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -30,6 +28,7 @@
import android.util.Log;
import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.ResourcesApkProvider;
import com.android.customization.model.theme.ThemeBundle.Builder;
import com.android.wallpaper.R;
@@ -39,7 +38,7 @@
/**
* Default implementation of {@link ThemeBundleProvider} that reads Themes' overlays from a stub APK.
*/
-public class DefaultThemeProvider implements ThemeBundleProvider {
+public class DefaultThemeProvider extends ResourcesApkProvider implements ThemeBundleProvider {
private static final String TAG = "DefaultThemeProvider";
@@ -60,37 +59,10 @@
private static final String CONFIG_BODY_FONT_FAMILY = "config_bodyFontFamily";
private static final String CONFIG_HEADLINE_FONT_FAMILY = "config_headlineFontFamily";
- private final Context mContext;
- private final String mStubPackageName;
- private Resources mStubApkResources;
private List<ThemeBundle> mThemes;
public DefaultThemeProvider(Context context) {
- mContext = context;
- mStubPackageName = mContext.getString(R.string.themes_stub_package);
- init();
- }
-
- private void init() {
- if (TextUtils.isEmpty(mStubPackageName)) {
- return;
- }
- mStubApkResources = null;
- try {
- PackageManager pm = mContext.getPackageManager();
- ApplicationInfo stubAppInfo = pm.getApplicationInfo(
- mStubPackageName, PackageManager.GET_META_DATA);
- if (stubAppInfo != null) {
- mStubApkResources = pm.getResourcesForApplication(stubAppInfo);
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, String.format("Themes stub APK for %s not found.", mStubPackageName));
- }
- }
-
- @Override
- public boolean isAvailable() {
- return mStubApkResources != null;
+ super(context, context.getString(R.string.themes_stub_package));
}
@Override
@@ -108,9 +80,7 @@
private void loadAll() {
addDefaultTheme();
- int themesListResId = mStubApkResources.getIdentifier(THEMES_ARRAY, "array",
- mStubPackageName);
- String[] themeNames = mStubApkResources.getStringArray(themesListResId);
+ String[] themeNames = getItemsFromStub(THEMES_ARRAY);
for (String themeName : themeNames) {
// Default theme needs special treatment (see #addDefaultTheme())
@@ -187,7 +157,6 @@
}
}
-
/**
* Default theme requires different treatment: if there are overlay packages specified in the
* stub apk, we'll use those, otherwise we'll get the System default values. But we cannot skip
@@ -270,9 +239,7 @@
}
private String getOverlayPackage(String prefix, String themeName) {
- int overlayPackageResId = mStubApkResources.getIdentifier(prefix + themeName,
- "string", mStubPackageName);
- return mStubApkResources.getString(overlayPackageResId);
+ return getItemStringFromStub(prefix, themeName);
}
private Typeface loadTypeface(String configName, String fontOverlayPackage)
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index cf6055c..15b9efc 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -15,9 +15,9 @@
*/
package com.android.customization.model.theme;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
@@ -83,7 +83,7 @@
}
@Override
- public boolean isCurrentlySet() {
+ public boolean isActive(Context context) {
return false;
}
diff --git a/src/com/android/customization/picker/BasePreviewAdapter.java b/src/com/android/customization/picker/BasePreviewAdapter.java
new file mode 100644
index 0000000..e87fb71
--- /dev/null
+++ b/src/com/android/customization/picker/BasePreviewAdapter.java
@@ -0,0 +1,112 @@
+/*
+ * 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.picker;
+
+import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.cardview.widget.CardView;
+import androidx.core.view.ViewCompat;
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
+import com.android.customization.widget.PreviewPager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base adapter for {@link PreviewPager}.
+ * It can be used as-is by creating an extension of {@link PreviewPage} and adding pages via
+ * {@link #addPage(PreviewPage)}
+ * @param <T> the type of {@link PreviewPage} that this adapter will handle.
+ */
+public class BasePreviewAdapter<T extends PreviewPage> extends PagerAdapter {
+
+ private final int mPreviewCardResId;
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+ protected final List<T> mPages = new ArrayList<>();
+
+ protected BasePreviewAdapter(Context context, @LayoutRes int previewCardResId) {
+ mContext = context;
+ mPreviewCardResId = previewCardResId;
+ mInflater = LayoutInflater.from(mContext);
+ }
+
+ protected void addPage(T p) {
+ mPages.add(p);
+ }
+
+ @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;
+ }
+ CardView card = (CardView) mInflater.inflate(mPreviewCardResId, container, false);
+ T 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) {
+ ((T) object).card = null;
+ }
+
+ /**
+ * Represents a possible page in a {@link PreviewPager}, based on a CardView container.
+ * Override {@link #bindPreviewContent()} to bind the contents of the page when instantiated.
+ */
+ public static abstract class PreviewPage {
+ protected final String title;
+ protected CardView card;
+
+ protected PreviewPage(String title) {
+ this.title = title;
+ }
+
+ public void setCard(CardView card) {
+ this.card = card;
+ }
+
+ public abstract void bindPreviewContent();
+ }
+}
diff --git a/src/com/android/customization/picker/CustomizationPickerActivity.java b/src/com/android/customization/picker/CustomizationPickerActivity.java
index f0db8a1..ab60166 100644
--- a/src/com/android/customization/picker/CustomizationPickerActivity.java
+++ b/src/com/android/customization/picker/CustomizationPickerActivity.java
@@ -30,12 +30,16 @@
import com.android.customization.model.CustomizationManager;
import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.clock.ClockManager;
+import com.android.customization.model.clock.Clockface;
+import com.android.customization.model.clock.ResourcesApkClockProvider;
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.clock.ClockFragment;
import com.android.customization.picker.grid.GridFragment;
import com.android.customization.picker.theme.ThemeFragment;
import com.android.wallpaper.R;
@@ -117,7 +121,11 @@
if (themeManager.isAvailable()) {
mSections.put(R.id.nav_theme, new ThemeSection(R.id.nav_theme, themeManager));
}
- // TODO: clock
+ //Clock
+ ClockManager clockManager = new ClockManager(this, new ResourcesApkClockProvider(this));
+ if (clockManager.isAvailable()) {
+ mSections.put(R.id.nav_clock, new ClockSection(R.id.nav_clock, clockManager));
+ }
//Grid
GridOptionsManager gridManager = new GridOptionsManager(
new LauncherGridOptionsProvider(this));
@@ -292,4 +300,22 @@
return mFragment;
}
}
+
+ private class ClockSection extends CustomizationSection<Clockface> {
+
+ private ClockFragment mFragment;
+
+ private ClockSection(int id, ClockManager manager) {
+ super(id, manager);
+ }
+
+ @Override
+ Fragment getFragment() {
+ if (mFragment == null) {
+ mFragment = ClockFragment.newInstance(getString(R.string.clock_title),
+ (ClockManager) mCustomizationManager);
+ }
+ return mFragment;
+ }
+ }
}
diff --git a/src/com/android/customization/picker/clock/ClockFragment.java b/src/com/android/customization/picker/clock/ClockFragment.java
new file mode 100644
index 0000000..3436c22
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ClockFragment.java
@@ -0,0 +1,128 @@
+/*
+ * 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.picker.clock;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.customization.model.clock.ClockManager;
+import com.android.customization.model.clock.Clockface;
+import com.android.customization.picker.BasePreviewAdapter;
+import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
+import com.android.customization.widget.OptionSelectorController;
+import com.android.customization.widget.PreviewPager;
+import com.android.wallpaper.R;
+import com.android.wallpaper.picker.ToolbarFragment;
+
+public class ClockFragment extends ToolbarFragment {
+
+ public static ClockFragment newInstance(CharSequence title, ClockManager manager) {
+ ClockFragment fragment = new ClockFragment();
+ fragment.setManager(manager);
+ fragment.setArguments(ToolbarFragment.createArguments(title));
+ return fragment;
+ }
+
+ private RecyclerView mOptionsContainer;
+ private OptionSelectorController mOptionsController;
+ private Clockface mSelectedOption;
+ private ClockManager mClockManager;
+ private PreviewPager mPreviewPager;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(
+ R.layout.fragment_clock_picker, container, /* attachToRoot */ false);
+ setUpToolbar(view);
+ mPreviewPager = view.findViewById(R.id.clock_preview_pager);
+ mOptionsContainer = view.findViewById(R.id.options_container);
+ setUpOptions();
+ view.findViewById(R.id.apply_button).setOnClickListener(v -> {
+ mClockManager.apply(mSelectedOption);
+ getActivity().finish();
+ });
+ return view;
+ }
+
+ private void setManager(ClockManager manager) {
+ mClockManager = manager;
+ }
+
+ private void createAdapter() {
+ mPreviewPager.setAdapter(new ClockPreviewAdapter(getContext(), mSelectedOption));
+ }
+
+ private void setUpOptions() {
+ mClockManager.fetchOptions(options -> {
+ mOptionsController = new OptionSelectorController(mOptionsContainer, options);
+
+ mOptionsController.addListener(selected -> {
+ mSelectedOption = (Clockface) selected;
+ createAdapter();
+ });
+ mOptionsController.initOptions();
+ for (Clockface option : options) {
+ if (option.isActive(getContext())) {
+ mSelectedOption = option;
+ }
+ }
+ // For development only, as there should always be a grid set.
+ if (mSelectedOption == null) {
+ mSelectedOption = options.get(0);
+ }
+ createAdapter();
+ });
+ }
+
+ private static class ClockfacePreviewPage extends PreviewPage {
+
+ private final Drawable mPreview;
+
+ public ClockfacePreviewPage(String title, Drawable previewDrawable) {
+ super(title);
+ mPreview = previewDrawable;
+ }
+
+ @Override
+ public void bindPreviewContent() {
+ ((ImageView) card.findViewById(R.id.clock_preview_image))
+ .setImageDrawable(mPreview);
+ }
+ }
+
+ /**
+ * 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 static class ClockPreviewAdapter extends BasePreviewAdapter<ClockfacePreviewPage> {
+ ClockPreviewAdapter(Context context, Clockface clockface) {
+ super(context, R.layout.clock_preview_card);
+ addPage(new ClockfacePreviewPage(clockface.getTitle(), clockface.getPreviewDrawable()));
+ }
+ }
+}
diff --git a/src/com/android/customization/picker/grid/GridFragment.java b/src/com/android/customization/picker/grid/GridFragment.java
index 5864c8e..6733cb5 100644
--- a/src/com/android/customization/picker/grid/GridFragment.java
+++ b/src/com/android/customization/picker/grid/GridFragment.java
@@ -15,9 +15,7 @@
*/
package com.android.customization.picker.grid;
-import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
-
-import android.content.Context;
+import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -26,13 +24,12 @@
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.picker.BasePreviewAdapter;
+import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
import com.android.customization.widget.OptionSelectorController;
import com.android.customization.widget.PreviewPager;
import com.android.wallpaper.R;
@@ -42,9 +39,6 @@
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.
*/
@@ -95,7 +89,7 @@
});
mOptionsController.initOptions();
for (GridOption option : options) {
- if (option.isCurrentlySet()) {
+ if (option.isActive(getContext())) {
mSelectedOption = option;
}
}
@@ -107,83 +101,42 @@
});
}
+ private static class GridPreviewPage extends PreviewPage {
+ private final int mPageId;
+ private final Asset mPreviewAsset;
+ private final int mCols;
+ private final int mRows;
+ private final Activity mActivity;
+
+ private GridPreviewPage(Activity activity, int id, Uri previewUri, int rows, int cols) {
+ super(null);
+ mPageId = id;
+ mPreviewAsset = new ContentUriAsset(activity, previewUri,
+ RequestOptions.fitCenterTransform());
+ mRows = rows;
+ mCols = cols;
+ mActivity = activity;
+ }
+
+ public void bindPreviewContent() {
+ mPreviewAsset.loadDrawable(mActivity, card.findViewById(R.id.grid_preview_image),
+ card.getContext().getResources().getColor(R.color.primary_color, null));
+ }
+ }
/**
* 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<>();
+ class GridPreviewAdapter extends BasePreviewAdapter<GridPreviewPage> {
GridPreviewAdapter(GridOption gridOption) {
+ super(getContext(), R.layout.grid_preview_card);
for (int i = 0; i < gridOption.previewPagesCount; i++) {
- mPages.add(new PreviewPage(getContext(), i,
+ addPage(new GridPreviewPage(getActivity(), 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 179c08f..aad4542 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -15,8 +15,7 @@
*/
package com.android.customization.picker.theme;
-import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
-
+import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -30,21 +29,18 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-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.theme.ThemeBundle;
import com.android.customization.model.theme.ThemeManager;
+import com.android.customization.picker.BasePreviewAdapter;
+import com.android.customization.picker.BasePreviewAdapter.PreviewPage;
import com.android.customization.widget.OptionSelectorController;
import com.android.customization.widget.PreviewPager;
import com.android.wallpaper.R;
import com.android.wallpaper.picker.ToolbarFragment;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Fragment that contains the main UI for selecting and applying a ThemeBundle.
*/
@@ -83,7 +79,7 @@
}
private void createAdapter() {
- mAdapter = new ThemePreviewAdapter(mSelectedTheme);
+ mAdapter = new ThemePreviewAdapter(getContext(), mSelectedTheme);
mPreviewPager.setAdapter(mAdapter);
}
@@ -97,7 +93,7 @@
});
mOptionsController.initOptions();
for (ThemeBundle theme : options) {
- if (theme.isCurrentlySet()) {
+ if (theme.isActive(getContext())) {
mSelectedTheme = theme;
}
}
@@ -110,43 +106,51 @@
createAdapter();
}
+ private static abstract class ThemePreviewPage extends PreviewPage {
+ @StringRes final int nameResId;
+ @DrawableRes final int iconSrc;
+ @LayoutRes final int contentLayoutRes;
+ @ColorInt final int accentColor;
+ private final LayoutInflater inflater;
+
+ private ThemePreviewPage(Context context, @StringRes int titleResId, @DrawableRes int iconSrc,
+ @LayoutRes int contentLayoutRes, @ColorInt int accentColor) {
+ super(null);
+ this.nameResId = titleResId;
+ this.iconSrc = iconSrc;
+ this.contentLayoutRes = contentLayoutRes;
+ this.accentColor = accentColor;
+ this.inflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public void bindPreviewContent() {
+ TextView header = card.findViewById(R.id.theme_preview_card_header);
+ header.setText(nameResId);
+ header.setCompoundDrawablesWithIntrinsicBounds(0, iconSrc, 0, 0);
+ header.setCompoundDrawableTintList(ColorStateList.valueOf(accentColor));
+
+ ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
+ inflater.inflate(contentLayoutRes, body, true);
+ bindBody();
+ }
+
+ protected abstract void bindBody();
+ }
+
/**
* 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 ThemePreviewAdapter extends PagerAdapter {
+ private static class ThemePreviewAdapter extends BasePreviewAdapter<ThemePreviewPage> {
- private abstract class PreviewPage {
- @StringRes final int nameResId;
- @DrawableRes final int iconSrc;
- @LayoutRes final int contentLayoutRes;
- @ColorInt final int accentColor;
-
- CardView card;
-
- private PreviewPage(@StringRes int titleResId, @DrawableRes int iconSrc,
- @LayoutRes int contentLayoutRes, @ColorInt int accentColor) {
- this.nameResId = titleResId;
- this.iconSrc = iconSrc;
- this.contentLayoutRes = contentLayoutRes;
- this.accentColor = accentColor;
- }
-
- public void setCard(CardView card) {
- this.card = card;
- }
-
- abstract void bindPreviewContent();
- }
-
- List<PreviewPage> mPages = new ArrayList<>();
-
- ThemePreviewAdapter(ThemeBundle theme) {
- mPages.add(new PreviewPage(R.string.preview_name_font, R.drawable.ic_font,
+ ThemePreviewAdapter(Context context, ThemeBundle theme) {
+ super(context, R.layout.theme_preview_card);
+ addPage(new ThemePreviewPage(context, R.string.preview_name_font, R.drawable.ic_font,
R.layout.preview_card_font_content, theme.getPreviewInfo().colorAccentLight) {
@Override
- void bindPreviewContent() {
+ protected void bindBody() {
TextView title = card.findViewById(R.id.font_card_title);
title.setTypeface(theme.getPreviewInfo().headlineFontFamily);
TextView body = card.findViewById(R.id.font_card_body);
@@ -154,49 +158,5 @@
}
});
}
-
- @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.theme_preview_card,
- container, false);
- PreviewPage page = mPages.get(position);
-
- TextView header = card.findViewById(R.id.theme_preview_card_header);
- header.setText(page.nameResId);
- header.setCompoundDrawablesWithIntrinsicBounds(0, page.iconSrc, 0, 0);
- header.setCompoundDrawableTintList(ColorStateList.valueOf(page.accentColor));
-
- ViewGroup body = card.findViewById(R.id.theme_preview_card_body_container);
- inflater.inflate(page.contentLayoutRes, body, true);
-
- 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/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index dc14cfc..8c17cad 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -100,7 +100,7 @@
@Override
public void onBindViewHolder(@NonNull TileViewHolder holder, int position) {
CustomizationOption option = mOptions.get(position);
- if (mSelectedOption == null && option.isCurrentlySet()) {
+ if (mSelectedOption == null && option.isActive(mContainer.getContext())) {
mSelectedOption = option;
}
if (holder.labelView != null) {