Merge tag 'android-11.0.0_r38' of https://android.googlesource.com/platform//packages/apps/ThemePicker into r

Android 11.0.0 Release 38 (RQ3A.210605.005)

Change-Id: If964d283cf218c890880e9a2a4c9c74f352764ac
diff --git a/Android.mk b/Android.mk
index 987c875..5a443d2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -49,8 +49,27 @@
 
 LOCAL_MANIFEST_FILE := AndroidManifest.xml
 
+LOCAL_REQUIRED_MODULES := default_permissions_com.android.wallpaper.xml privapp_whitelist_com.android.wallpaper.xml
+
 include $(BUILD_PACKAGE)
 
+include $(CLEAR_VARS)
+LOCAL_MODULE := default_permissions_com.android.wallpaper.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT_ETC)/default-permissions
+LOCAL_SYSTEM_EXT_MODULE := true
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := privapp_whitelist_com.android.wallpaper.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT_ETC)/permissions
+LOCAL_SYSTEM_EXT_MODULE := true
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
 
 # ==================================================
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1bcacab..d106de0 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -9,6 +9,10 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
+    <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
 
     <application
         tools:replace="android:icon,android:name"
@@ -25,7 +29,8 @@
             android:name="com.android.customization.picker.CustomizationPickerActivity"
             android:label="@string/app_name"
             android:resizeableActivity="false"
-            android:theme="@style/CustomizationTheme.NoActionBar">
+            android:exported="true"
+            android:theme="@style/CustomizationTheme.NoActionBar.CustomizationPicker">
             <intent-filter>
                 <action android:name="android.intent.action.SET_WALLPAPER"/>
 
@@ -39,6 +44,16 @@
         <activity android:name="com.android.customization.picker.ViewOnlyFullPreviewActivity"
             android:resizeableActivity="false"
             android:theme="@style/CustomizationTheme.NoActionBar"/>
+        <activity
+            android:name="com.android.customization.picker.LockClockPickerActivity"
+            android:label="@string/app_name"
+            android:resizeableActivity="true"
+            android:theme="@style/CustomizationTheme.NoActionBar">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
     </application>
 
 </manifest>
diff --git a/default_permissions_com.android.wallpaper.xml b/default_permissions_com.android.wallpaper.xml
new file mode 100644
index 0000000..41b23ce
--- /dev/null
+++ b/default_permissions_com.android.wallpaper.xml
@@ -0,0 +1,37 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<!--
+     Copyright (C) 2019-2020 The LineageOS 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.
+-->
+
+<!--
+    This file contains permissions to be granted by default. Default
+    permissions are granted to special platform components and to apps
+    that are approved to get default grants. The special components
+    are apps that are expected to work out-of-the-box as they provide
+    core use cases such as default dialer, default email, etc. These
+    grants are managed by the platform. The apps that are additionally
+    approved for default grants are ones that provide carrier specific
+    functionality, ones legally required at some location, ones providing
+    alternative disclosure and opt-out UI, ones providing highlight features
+    of a dedicated device, etc. This file contains only the latter exceptions.
+    Fixed permissions cannot be controlled by the user and need a special
+    approval. Typically these are to ensure either legally mandated functions
+    or the app is considered a part of the OS.
+-->
+<exceptions>
+    <exception package="com.android.wallpaper">
+        <permission name="android.permission.READ_EXTERNAL_STORAGE" fixed="false"/>
+    </exception>
+</exceptions>
diff --git a/privapp_whitelist_com.android.wallpaper.xml b/privapp_whitelist_com.android.wallpaper.xml
new file mode 100644
index 0000000..abcec3d
--- /dev/null
+++ b/privapp_whitelist_com.android.wallpaper.xml
@@ -0,0 +1,25 @@
+<?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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.wallpaper">
+        <permission name="android.permission.BIND_WALLPAPER"/>
+        <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+        <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
+        <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/res/layout/fragment_clock_picker.xml b/res/layout/fragment_clock_picker.xml
index 6b9f94c..5a0ebad 100644
--- a/res/layout/fragment_clock_picker.xml
+++ b/res/layout/fragment_clock_picker.xml
@@ -35,7 +35,7 @@
             <com.android.wallpaper.widget.PreviewPager
                 android:id="@+id/clock_preview_pager"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="0dp"
                 android:background="@color/preview_pager_background"
                 app:layout_constrainedHeight="true"
                 app:layout_constraintBottom_toTopOf="@id/options_container"
@@ -50,7 +50,7 @@
             <androidx.recyclerview.widget.RecyclerView
                 android:id="@+id/options_container"
                 android:layout_width="match_parent"
-                android:layout_height="@dimen/options_container_height"
+                android:layout_height="match_parent"
                 android:layout_gravity="center_horizontal"
                 android:layout_marginTop="10dp"
                 app:layout_constraintBottom_toTopOf="@id/placeholder"
diff --git a/res/layout/fragment_custom_theme_name.xml b/res/layout/fragment_custom_theme_name.xml
index 532e904..28dffd3 100644
--- a/res/layout/fragment_custom_theme_name.xml
+++ b/res/layout/fragment_custom_theme_name.xml
@@ -80,6 +80,7 @@
                         android:layout_gravity="center|top"
                         android:importantForAutofill="no"
                         android:minWidth="300dp"
+                        android:inputType="textCapWords"
                         style="@style/CustomThemeNameEditText"/>
             </LinearLayout>
         </ScrollView>
diff --git a/res/layout/theme_component_preview.xml b/res/layout/theme_component_preview.xml
index bf3255d..fc3e474 100644
--- a/res/layout/theme_component_preview.xml
+++ b/res/layout/theme_component_preview.xml
@@ -48,6 +48,7 @@
             android:layout_gravity="center_horizontal"
             android:drawablePadding="@dimen/theme_preview_header_drawable_padding"
             android:textAppearance="@style/CardTitleTextAppearance"
+            android:gravity="center_horizontal"
             android:importantForAccessibility="no"
             app:layout_constraintBottom_toTopOf="@id/theme_preview_card_body_container"
             app:layout_constraintEnd_toEndOf="parent"
diff --git a/res/values/override.xml b/res/values/override.xml
index a070dbe..b0723e1 100644
--- a/res/values/override.xml
+++ b/res/values/override.xml
@@ -17,7 +17,6 @@
 -->
 <resources>
     <string name="themes_stub_package" translatable="false"/>
-    <string name="clocks_stub_package" translatable="false"/>
     <!-- Authority of a provider in System UI that will provide preview info for available clockfaces. -->
     <string name="clocks_provider_authority" translatable="false">com.android.keyguard.clock</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b4e1971..0948012 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -38,7 +38,9 @@
         <item name="android:windowNoTitle">true</item>
         <item name="android:fitsSystemWindows">false</item>
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+    </style>
 
+    <style name="CustomizationTheme.NoActionBar.CustomizationPicker" parent="CustomizationTheme.NoActionBar">
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowDisablePreview">true</item>
diff --git a/src/com/android/customization/model/clock/ContentProviderClockProvider.java b/src/com/android/customization/model/clock/ContentProviderClockProvider.java
index 8f4c031..ad60ddc 100644
--- a/src/com/android/customization/model/clock/ContentProviderClockProvider.java
+++ b/src/com/android/customization/model/clock/ContentProviderClockProvider.java
@@ -2,9 +2,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
@@ -27,7 +25,6 @@
     private final Context mContext;
     private final ProviderInfo mProviderInfo;
     private List<Clockface> mClocks;
-    private boolean mClockContentAvailable;
 
     public ContentProviderClockProvider(Context context) {
         mContext = context;
@@ -36,25 +33,11 @@
         mProviderInfo = TextUtils.isEmpty(providerAuthority) ? null
                 : mContext.getPackageManager().resolveContentProvider(providerAuthority,
                         PackageManager.MATCH_SYSTEM_ONLY);
-
-        if (TextUtils.isEmpty(mContext.getString(R.string.clocks_stub_package))) {
-            mClockContentAvailable = false;
-        } else {
-            try {
-                ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
-                        mContext.getString(R.string.clocks_stub_package),
-                        PackageManager.MATCH_SYSTEM_ONLY);
-                mClockContentAvailable = applicationInfo != null;
-            } catch (NameNotFoundException e) {
-                mClockContentAvailable = false;
-            }
-        }
     }
 
     @Override
     public boolean isAvailable() {
-        return mProviderInfo != null && mClockContentAvailable
-                && (mClocks == null || !mClocks.isEmpty());
+        return mProviderInfo != null && (mClocks == null || !mClocks.isEmpty());
     }
 
     @Override
diff --git a/src/com/android/customization/model/grid/GridOption.java b/src/com/android/customization/model/grid/GridOption.java
index 43afee4..b62a4ee 100644
--- a/src/com/android/customization/model/grid/GridOption.java
+++ b/src/com/android/customization/model/grid/GridOption.java
@@ -62,7 +62,7 @@
         mTitle = title;
         mIsCurrent = isCurrent;
         mIconShapePath = iconShapePath;
-        mTileDrawable = new GridTileDrawable(rows, cols, mIconShapePath);
+        mTileDrawable = new GridTileDrawable(cols, rows, iconShapePath);
         this.name = name;
         this.rows = rows;
         this.cols = cols;
diff --git a/src/com/android/customization/model/theme/OverlayThemeExtractor.java b/src/com/android/customization/model/theme/OverlayThemeExtractor.java
index 816176e..395734a 100644
--- a/src/com/android/customization/model/theme/OverlayThemeExtractor.java
+++ b/src/com/android/customization/model/theme/OverlayThemeExtractor.java
@@ -149,8 +149,12 @@
     void addIconOverlay(Builder builder, String packageName, String... previewIcons)
             throws NameNotFoundException {
         builder.addOverlayPackage(getOverlayCategory(packageName), packageName);
-        for (String iconName : previewIcons) {
-            builder.addIcon(loadIconPreviewDrawable(iconName, packageName, false));
+        try {
+            for (String iconName : previewIcons) {
+                builder.addIcon(loadIconPreviewDrawable(iconName, packageName, false));
+            }
+        } catch (NameNotFoundException | NotFoundException e) {
+            Log.w(TAG, "Didn't find overlay icons, will skip preview", e);
         }
     }
 
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index 533fbd0..0fae521 100644
--- a/src/com/android/customization/model/theme/ThemeManager.java
+++ b/src/com/android/customization/model/theme/ThemeManager.java
@@ -74,7 +74,7 @@
 
     @Override
     public boolean isAvailable() {
-        return mOverlayManagerCompat.isAvailable() && mProvider.isAvailable();
+        return false;
     }
 
     @Override
diff --git a/src/com/android/customization/model/theme/custom/IconOptionsProvider.java b/src/com/android/customization/model/theme/custom/IconOptionsProvider.java
index f7b669b..1457d54 100644
--- a/src/com/android/customization/model/theme/custom/IconOptionsProvider.java
+++ b/src/com/android/customization/model/theme/custom/IconOptionsProvider.java
@@ -24,6 +24,7 @@
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_THEMEPICKER;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
@@ -71,6 +72,7 @@
     protected void loadOptions() {
         addDefault();
 
+        PackageManager pm = mContext.getPackageManager();
         Map<String, IconOption> optionsByPrefix = new HashMap<>();
         for (String overlayPackage : mOverlayPackages) {
             IconOption option = addOrUpdateOption(optionsByPrefix, overlayPackage,
@@ -79,6 +81,8 @@
                 for (String iconName : ICONS_FOR_PREVIEW) {
                     option.addIcon(loadIconPreviewDrawable(iconName, overlayPackage));
                 }
+
+                option.setLabel(pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString());
             } catch (NotFoundException | NameNotFoundException e) {
                 Log.w(TAG, String.format("Couldn't load icon overlay details for %s, will skip it",
                         overlayPackage), e);
@@ -104,7 +108,6 @@
         for (IconOption option : optionsByPrefix.values()) {
             if (option.isValid(mContext)) {
                 mOptions.add(option);
-                option.setLabel(mContext.getString(R.string.icon_component_label, mOptions.size()));
             }
         }
     }
diff --git a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
index f93b892..633c1f5 100644
--- a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
+++ b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
@@ -55,6 +55,8 @@
 public class ShapeOptionsProvider extends ThemeComponentOptionProvider<ShapeOption> {
 
     private static final String TAG = "ShapeOptionsProvider";
+    private static final int MAX_ICON_SHAPE_PREVIEWS = 6;
+
     private final String[] mShapePreviewIconPackages;
     private int mThumbSize;
 
@@ -106,6 +108,10 @@
     private List<ShapeAppIcon> getShapedAppIcons(Path path) {
         List<ShapeAppIcon> shapedAppIcons = new ArrayList<>();
         for (String packageName : mShapePreviewIconPackages) {
+            if (shapedAppIcons.size() == MAX_ICON_SHAPE_PREVIEWS) {
+                break;
+            }
+
             Drawable icon = null;
             CharSequence name = null;
             try {
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
index 5922f5c..327ff65 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -151,6 +151,9 @@
             }
             TextView title = container.findViewById(R.id.font_card_title);
             title.setTypeface(mHeadlineFont);
+            TextView header = container.findViewById(R.id.theme_preview_card_header);
+            header.setText(String.format("%s\n(%s)",
+                    container.getContext().getString(R.string.preview_name_font), mLabel));
             TextView bodyText = container.findViewById(R.id.font_card_body);
             bodyText.setTypeface(mBodyFont);
             container.findViewById(R.id.font_card_divider).setBackgroundColor(
@@ -233,6 +236,9 @@
 
             bindPreviewHeader(container, R.string.preview_name_icon, R.drawable.ic_wifi_24px);
 
+            TextView header = container.findViewById(R.id.theme_preview_card_header);
+            header.setText(String.format("%s\n(%s)",
+                    container.getContext().getString(R.string.preview_name_icon), mLabel));
             ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
             if (cardBody.getChildCount() == 0) {
                 LayoutInflater.from(container.getContext()).inflate(
@@ -366,6 +372,9 @@
 
             bindPreviewHeader(container, R.string.preview_name_color, R.drawable.ic_colorize_24px);
 
+            TextView header = container.findViewById(R.id.theme_preview_card_header);
+            header.setText(String.format("%s\n(%s)",
+                    container.getContext().getString(R.string.preview_name_color), mLabel));
             ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
             if (cardBody.getChildCount() == 0) {
                 LayoutInflater.from(container.getContext()).inflate(
@@ -509,6 +518,9 @@
 
             bindPreviewHeader(container, R.string.preview_name_shape, R.drawable.ic_shapes_24px);
 
+            TextView header = container.findViewById(R.id.theme_preview_card_header);
+            header.setText(String.format("%s\n(%s)",
+                    container.getContext().getString(R.string.preview_name_shape), mLabel));
             ViewGroup cardBody = container.findViewById(R.id.theme_preview_card_body_container);
             if (cardBody.getChildCount() == 0) {
                 LayoutInflater.from(container.getContext()).inflate(
diff --git a/src/com/android/customization/picker/CustomizationPickerActivity.java b/src/com/android/customization/picker/CustomizationPickerActivity.java
index 786bebd..f24a7c7 100644
--- a/src/com/android/customization/picker/CustomizationPickerActivity.java
+++ b/src/com/android/customization/picker/CustomizationPickerActivity.java
@@ -91,7 +91,7 @@
  */
 public class CustomizationPickerActivity extends FragmentActivity implements WallpapersUiContainer,
         CategoryFragmentHost, ThemeFragmentHost, GridFragmentHost, ClockFragmentHost,
-        BottomActionBarHost, FragmentTransactionChecker {
+        BottomActionBarHost, FragmentTransactionChecker, PermissionChangedListener {
 
     public static final String WALLPAPER_FLAVOR_EXTRA =
             "com.android.launcher3.WALLPAPER_FLAVOR";
@@ -154,6 +154,9 @@
                     WALLPAPER_FOCUS.equals(getIntent().getStringExtra(WALLPAPER_FLAVOR_EXTRA))
                             ? R.id.nav_wallpaper : R.id.nav_theme);
         }
+        if (!isReadExternalStoragePermissionGranted()) {
+            requestExternalStoragePermission(this);
+        }
     }
 
     @Override
@@ -463,6 +466,14 @@
         return mIsSafeToCommitFragmentTransaction;
     }
 
+    @Override
+    public void onPermissionsGranted() {
+    }
+
+    @Override
+    public void onPermissionsDenied(boolean dontAskAgain) {
+    }
+
     /**
      * Represents a section of the Picker (eg "ThemeBundle", "Clock", etc).
      * There should be a concrete subclass per available section, providing the corresponding
diff --git a/src/com/android/customization/picker/LockClockPickerActivity.java b/src/com/android/customization/picker/LockClockPickerActivity.java
new file mode 100644
index 0000000..3b61f44
--- /dev/null
+++ b/src/com/android/customization/picker/LockClockPickerActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.content.Intent;
+import android.os.Bundle;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import com.android.customization.model.clock.Clockface;
+import com.android.customization.model.clock.ClockManager;
+import com.android.customization.model.clock.ContentProviderClockProvider;
+import com.android.customization.module.CustomizationInjector;
+import com.android.customization.module.ThemesUserEventLogger;
+import com.android.customization.picker.clock.ClockFragment;
+import com.android.customization.picker.clock.ClockFragment.ClockFragmentHost;
+import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.R;
+
+/**
+ * Activity allowing for the clock face picker to be linked to from other setup flows.
+ *
+ * This should be used with startActivityForResult. The resulting intent contains an extra
+ * "clock_face_name" with the id of the picked clock face.
+ */
+public class LockClockPickerActivity extends FragmentActivity implements ClockFragmentHost {
+
+    private ClockManager mClockManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_clock_face_picker);
+
+        CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
+        ThemesUserEventLogger eventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(
+                this);
+
+        mClockManager = new ClockManager(getContentResolver(),
+                new ContentProviderClockProvider(this), eventLogger);
+        if (!mClockManager.isAvailable()) {
+            finish();
+        } else {
+            final FragmentManager fm = getSupportFragmentManager();
+            final FragmentTransaction fragmentTransaction = fm.beginTransaction();
+            final ClockFragment clockFragment = ClockFragment.newInstance(
+                    getString(R.string.clock_title));
+            fragmentTransaction.replace(R.id.fragment_container, clockFragment);
+            fragmentTransaction.commitNow();
+        }
+    }
+
+    @Override
+    public ClockManager getClockManager() {
+        return mClockManager;
+    }
+}
diff --git a/src/com/android/customization/widget/GridTileDrawable.java b/src/com/android/customization/widget/GridTileDrawable.java
index c746aaf..6d58503 100644
--- a/src/com/android/customization/widget/GridTileDrawable.java
+++ b/src/com/android/customization/widget/GridTileDrawable.java
@@ -28,7 +28,8 @@
     private final Path mTransformedPath;
     private final Matrix mScaleMatrix;
     private float mCellSize = -1f;
-    private float mSpaceBetweenIcons;
+    private float mMarginTop;
+    private float mMarginLeft;
 
     public GridTileDrawable(int cols, int rows, String path) {
         mCols = cols;
@@ -41,9 +42,15 @@
 
     @Override
     protected void onBoundsChange(Rect bounds) {
+        float spaceBetweenIcons;
+
         super.onBoundsChange(bounds);
-        mCellSize = (float) bounds.height() / mRows;
-        mSpaceBetweenIcons = mCellSize * ((1 - ICON_SCALE) / 2);
+        mCellSize = Math.min((float) bounds.height() / mRows,  (float) bounds.width() / mCols);
+
+        spaceBetweenIcons = mCellSize * ((1 - ICON_SCALE) / 2);
+        mMarginTop = (bounds.height() - mCellSize * mRows) / 2 + spaceBetweenIcons;
+        mMarginLeft = (bounds.width() - mCellSize * mCols) / 2 + spaceBetweenIcons;
+
 
         float scaleFactor = (mCellSize * ICON_SCALE) / PATH_SIZE;
         mScaleMatrix.setScale(scaleFactor, scaleFactor);
@@ -55,8 +62,8 @@
         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;
+                float x = (c * mCellSize) + mMarginLeft;
+                float y = (r * mCellSize) + mMarginTop;
                 canvas.translate(x, y);
                 canvas.drawPath(mTransformedPath, mPaint);
                 canvas.restoreToCount(saveCount);