Merge "Architecture review of Copyable Slice"
diff --git a/res/drawable/ic_content_copy_grey600_24dp.xml b/res/drawable/ic_content_copy_grey600_24dp.xml
new file mode 100644
index 0000000..827c66e
--- /dev/null
+++ b/res/drawable/ic_content_copy_grey600_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"
+      android:fillColor="#757575"/>
+</vector>
diff --git a/src/com/android/settings/slices/CopyableSlice.java b/src/com/android/settings/slices/CopyableSlice.java
new file mode 100644
index 0000000..31fc151
--- /dev/null
+++ b/src/com/android/settings/slices/CopyableSlice.java
@@ -0,0 +1,28 @@
+/*
+ * 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.settings.slices;
+
+/**
+ * Provide the copy ability for preference controller to copy the data to the clipboard.
+ */
+public interface CopyableSlice {
+    /**
+     * Copy the key slice information to the clipboard.
+     * It is highly recommended to show the toast to notify users when implemented this function.
+     */
+    void copy();
+}
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 33576f5..fa669bb 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -107,6 +107,12 @@
             "com.android.settings.slice.action.SLIDER_CHANGED";
 
     /**
+     * Action passed for copy data for the Copyable Slices.
+     */
+    public static final String ACTION_COPY =
+            "com.android.settings.slice.action.COPY";
+
+    /**
      * Intent Extra passed for the key identifying the Setting Slice.
      */
     public static final String EXTRA_SLICE_KEY = "com.android.settings.slice.extra.key";
diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java
index e9e9d2c..4b86f33 100644
--- a/src/com/android/settings/slices/SliceBroadcastReceiver.java
+++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java
@@ -24,6 +24,7 @@
 import static com.android.settings.notification.ZenModeSliceBuilder.ACTION_ZEN_MODE_SLICE_CHANGED;
 import static com.android.settings.slices.SettingsSliceProvider.ACTION_SLIDER_CHANGED;
 import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CHANGED;
+import static com.android.settings.slices.SettingsSliceProvider.ACTION_COPY;
 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED;
 import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED;
@@ -115,6 +116,9 @@
             case ACTION_FLASHLIGHT_SLICE_CHANGED:
                 FlashlightSliceBuilder.handleUriChange(context, intent);
                 break;
+            case ACTION_COPY:
+                handleCopyAction(context, key, isPlatformSlice);
+                break;
         }
     }
 
@@ -184,6 +188,29 @@
         updateUri(context, key, isPlatformSlice);
     }
 
+    private void handleCopyAction(Context context, String key, boolean isPlatformSlice) {
+        if (TextUtils.isEmpty(key)) {
+            throw new IllegalArgumentException("No key passed to Intent for controller");
+        }
+
+        final BasePreferenceController controller = getPreferenceController(context, key);
+
+        if (!(controller instanceof CopyableSlice)) {
+            throw new IllegalArgumentException(
+                    "Copyable action passed for a non-copyable key:" + key);
+        }
+
+        if (!controller.isAvailable()) {
+            Log.w(TAG, "Can't update " + key + " since the setting is unavailable");
+            if (!controller.hasAsyncUpdate()) {
+                updateUri(context, key, isPlatformSlice);
+            }
+            return;
+        }
+
+        ((CopyableSlice) controller).copy();
+    }
+
     /**
      * Log Slice value update events into MetricsFeatureProvider. The logging schema generally
      * follows the pattern in SharedPreferenceLogger.
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index 2432cc6..f6cc57f 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -93,6 +93,10 @@
             return buildUnavailableSlice(context, sliceData);
         }
 
+        if (controller instanceof CopyableSlice) {
+            return buildCopyableSlice(context, sliceData, controller);
+        }
+
         switch (sliceData.getSliceType()) {
             case SliceData.SliceType.INTENT:
                 return buildIntentSlice(context, sliceData, controller);
@@ -324,6 +328,28 @@
                 .build();
     }
 
+    private static Slice buildCopyableSlice(Context context, SliceData sliceData,
+            BasePreferenceController controller) {
+        final SliceAction copyableAction = getCopyableAction(context, sliceData);
+        final PendingIntent contentIntent = getContentPendingIntent(context, sliceData);
+        final IconCompat icon = getSafeIcon(context, sliceData);
+        final SliceAction primaryAction = new SliceAction(contentIntent, icon,
+                sliceData.getTitle());
+        final CharSequence subtitleText = getSubtitleText(context, controller, sliceData);
+        @ColorInt final int color = Utils.getColorAccentDefaultColor(context);
+        final Set<String> keywords = buildSliceKeywords(sliceData);
+
+        return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY)
+                .setAccentColor(color)
+                .addRow(new RowBuilder()
+                        .setTitle(sliceData.getTitle())
+                        .setSubtitle(subtitleText)
+                        .setPrimaryAction(primaryAction)
+                        .addEndItem(copyableAction))
+                .setKeywords(keywords)
+                .build();
+    }
+
     private static BasePreferenceController getPreferenceController(Context context,
             String controllerClassName, String controllerKey) {
         try {
@@ -346,6 +372,14 @@
         return getActionIntent(context, SettingsSliceProvider.ACTION_SLIDER_CHANGED, sliceData);
     }
 
+    private static SliceAction getCopyableAction(Context context, SliceData sliceData) {
+        final PendingIntent intent = getActionIntent(context,
+                SettingsSliceProvider.ACTION_COPY, sliceData);
+        final IconCompat icon = IconCompat.createWithResource(context,
+                R.drawable.ic_content_copy_grey600_24dp);
+        return new SliceAction(intent, icon, sliceData.getTitle());
+    }
+
     private static boolean isValidSummary(Context context, CharSequence summary) {
         if (summary == null || TextUtils.isEmpty(summary.toString().trim())) {
             return false;
diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
index 1055667..4371278 100644
--- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java
@@ -41,6 +41,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeCopyableController;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.FakeSliderController;
 import com.android.settings.testutils.FakeToggleController;
@@ -67,6 +68,7 @@
     private final Uri URI = Uri.parse("content://com.android.settings.slices/test");
     private final Class TOGGLE_CONTROLLER = FakeToggleController.class;
     private final Class SLIDER_CONTROLLER = FakeSliderController.class;
+    private final Class COPYABLE_CONTROLLER = FakeCopyableController.class;
     private final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class;
     private final boolean IS_DYNAMIC_SUMMARY_ALLOWED = false;
 
@@ -116,7 +118,6 @@
     public void buildSliderSlice_returnsMatchingSlice() {
         final SliceData data = getDummyData(SLIDER_CONTROLLER, SliceData.SliceType.SLIDER);
 
-
         final Slice slice = SliceBuilderUtils.buildSlice(mContext, data);
         verify(mFeatureFactory.metricsFeatureProvider)
                 .action(eq(mContext), eq(MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED),
@@ -131,6 +132,23 @@
     }
 
     @Test
+    public void buildCopyableSlice_returnsMatchingSlice() {
+        final SliceData dummyData = getDummyData(COPYABLE_CONTROLLER, -1);
+
+        final Slice slice = SliceBuilderUtils.buildSlice(mContext, dummyData);
+        verify(mFeatureFactory.metricsFeatureProvider)
+                .action(eq(mContext), eq(MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED),
+                        mLoggingArgumentCatpor.capture());
+        final Pair<Integer, Object> capturedLoggingPair = mLoggingArgumentCatpor.getValue();
+
+        assertThat(capturedLoggingPair.first)
+                .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME);
+        assertThat(capturedLoggingPair.second)
+                .isEqualTo(dummyData.getKey());
+        SliceTester.testSettingsCopyableSlice(mContext, slice, dummyData);
+    }
+
+    @Test
     public void testUriBuilder_oemAuthority_intentPath_returnsValidSliceUri() {
         final Uri expectedUri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeCopyableController.java b/tests/robotests/src/com/android/settings/testutils/FakeCopyableController.java
new file mode 100644
index 0000000..a02377c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/FakeCopyableController.java
@@ -0,0 +1,44 @@
+/*
+ * 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.settings.testutils;
+
+import android.content.Context;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.slices.CopyableSlice;
+
+public class FakeCopyableController extends BasePreferenceController implements
+        CopyableSlice {
+
+    public FakeCopyableController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public boolean isSliceable() {
+        return true;
+    }
+
+    @Override
+    public void copy() {
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/SliceTester.java b/tests/robotests/src/com/android/settings/testutils/SliceTester.java
index 892f948..be4199d 100644
--- a/tests/robotests/src/com/android/settings/testutils/SliceTester.java
+++ b/tests/robotests/src/com/android/settings/testutils/SliceTester.java
@@ -169,6 +169,43 @@
     }
 
     /**
+     * Test the copyable slice, including:
+     * - No intent
+     * - Correct title
+     * - Correct intent
+     * - Correct keywords
+     * - TTL
+     * - Color
+     */
+    public static void testSettingsCopyableSlice(Context context, Slice slice,
+            SliceData sliceData) {
+        final SliceMetadata metadata = SliceMetadata.from(context, slice);
+
+        final SliceItem colorItem = SliceQuery.findSubtype(slice, FORMAT_INT, SUBTYPE_COLOR);
+        final int color = colorItem.getInt();
+        assertThat(color).isEqualTo(Utils.getColorAccentDefaultColor(context));
+
+        final SliceAction primaryAction = metadata.getPrimaryAction();
+
+        final IconCompat expectedIcon = IconCompat.createWithResource(context,
+                sliceData.getIconResource());
+        assertThat(expectedIcon.toString()).isEqualTo(primaryAction.getIcon().toString());
+
+        final long sliceTTL = metadata.getExpiry();
+        assertThat(sliceTTL).isEqualTo(ListBuilder.INFINITY);
+
+        // Check primary intent
+        final PendingIntent primaryPendingIntent = primaryAction.getAction();
+        assertThat(primaryPendingIntent).isEqualTo(
+                SliceBuilderUtils.getContentPendingIntent(context, sliceData));
+
+        final List<SliceItem> sliceItems = slice.getItems();
+        assertTitle(sliceItems, sliceData.getTitle());
+
+        assertKeywords(metadata, sliceData);
+    }
+
+    /**
      * Test the contents of an unavailable slice, including:
      * - No toggles
      * - Correct title
@@ -229,4 +266,4 @@
         expectedKeywords.add(data.getScreenTitle().toString());
         assertThat(keywords).containsExactlyElementsIn(expectedKeywords);
     }
-}
\ No newline at end of file
+}