OSDN Git Service

be2e8df63e1d8334c7168ca7eb6361093fa568d7
[android-x86/packages-apps-Settings.git] / src / com / android / settings / slices / SliceBuilderUtils.java
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16
17 package com.android.settings.slices;
18
19 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
20 import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
21 import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING;
22 import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER;
23 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
24 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
25 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_PLATFORM_DEFINED;
26
27 import android.app.PendingIntent;
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.net.Uri;
33 import android.provider.Settings;
34 import android.provider.SettingsSlicesContract;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.util.Pair;
38
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41 import com.android.settings.R;
42 import com.android.settings.SettingsActivity;
43 import com.android.settings.SubSettings;
44 import com.android.settings.core.BasePreferenceController;
45 import com.android.settings.core.SliderPreferenceController;
46 import com.android.settings.core.TogglePreferenceController;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settings.search.DatabaseIndexingUtils;
49 import com.android.settingslib.core.AbstractPreferenceController;
50
51 import android.support.v4.graphics.drawable.IconCompat;
52
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.List;
56
57 import androidx.slice.Slice;
58 import androidx.slice.builders.ListBuilder;
59 import androidx.slice.builders.SliceAction;
60
61
62 /**
63  * Utility class to build Slices objects and Preference Controllers based on the Database managed
64  * by {@link SlicesDatabaseHelper}
65  */
66 public class SliceBuilderUtils {
67
68     private static final String TAG = "SliceBuilder";
69
70     // A Slice should not be store for longer than 60,000 milliseconds / 1 minute.
71     public static final long SLICE_TTL_MILLIS = 60000;
72
73     /**
74      * Build a Slice from {@link SliceData}.
75      *
76      * @return a {@link Slice} based on the data provided by {@param sliceData}.
77      * Will build an {@link Intent} based Slice unless the Preference Controller name in
78      * {@param sliceData} is an inline controller.
79      */
80     public static Slice buildSlice(Context context, SliceData sliceData) {
81         Log.d(TAG, "Creating slice for: " + sliceData.getPreferenceController());
82         final BasePreferenceController controller = getPreferenceController(context, sliceData);
83         final Pair<Integer, Object> sliceNamePair =
84                 Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, sliceData.getKey());
85         // Log Slice requests using the same schema as SharedPreferenceLogger (but with a different
86         // action name).
87         FeatureFactory.getFactory(context).getMetricsFeatureProvider()
88                 .action(context, MetricsEvent.ACTION_SETTINGS_SLICE_REQUESTED, sliceNamePair);
89
90         if (controller.getAvailabilityStatus() != AVAILABLE) {
91             return buildUnavailableSlice(context, sliceData, controller);
92         }
93
94         switch (sliceData.getSliceType()) {
95             case SliceData.SliceType.INTENT:
96                 return buildIntentSlice(context, sliceData, controller);
97             case SliceData.SliceType.SWITCH:
98                 return buildToggleSlice(context, sliceData, controller);
99             case SliceData.SliceType.SLIDER:
100                 return buildSliderSlice(context, sliceData, controller);
101             default:
102                 throw new IllegalArgumentException(
103                         "Slice type passed was invalid: " + sliceData.getSliceType());
104         }
105     }
106
107     /**
108      * @return the {@link SliceData.SliceType} for the {@param controllerClassName} and key.
109      */
110     @SliceData.SliceType
111     public static int getSliceType(Context context, String controllerClassName,
112             String controllerKey) {
113         BasePreferenceController controller = getPreferenceController(context, controllerClassName,
114                 controllerKey);
115         return controller.getSliceType();
116     }
117
118     /**
119      * Splits the Settings Slice Uri path into its two expected components:
120      * - intent/action
121      * - key
122      * <p>
123      * Examples of valid paths are:
124      * - /intent/wifi
125      * - /intent/bluetooth
126      * - /action/wifi
127      * - /action/accessibility/servicename
128      *
129      * @param uri of the Slice. Follows pattern outlined in {@link SettingsSliceProvider}.
130      * @return Pair whose first element {@code true} if the path is prepended with "intent", and
131      * second is a key.
132      */
133     public static Pair<Boolean, String> getPathData(Uri uri) {
134         final String path = uri.getPath();
135         final String[] split = path.split("/", 3);
136
137         // Split should be: [{}, SLICE_TYPE, KEY].
138         // Example: "/action/wifi" -> [{}, "action", "wifi"]
139         //          "/action/longer/path" -> [{}, "action", "longer/path"]
140         if (split.length != 3) {
141             return null;
142         }
143
144         final boolean isIntent = TextUtils.equals(SettingsSlicesContract.PATH_SETTING_INTENT,
145                 split[1]);
146
147         return new Pair<>(isIntent, split[2]);
148     }
149
150     /**
151      * Looks at the controller classname in in {@link SliceData} from {@param sliceData}
152      * and attempts to build an {@link AbstractPreferenceController}.
153      */
154     public static BasePreferenceController getPreferenceController(Context context,
155             SliceData sliceData) {
156         return getPreferenceController(context, sliceData.getPreferenceController(),
157                 sliceData.getKey());
158     }
159
160     /**
161      * @return {@link PendingIntent} for a non-primary {@link SliceAction}.
162      */
163     public static PendingIntent getActionIntent(Context context, String action, SliceData data) {
164         final Intent intent = new Intent(action);
165         intent.setClass(context, SliceBroadcastReceiver.class);
166         intent.putExtra(EXTRA_SLICE_KEY, data.getKey());
167         intent.putExtra(EXTRA_SLICE_PLATFORM_DEFINED, data.isPlatformDefined());
168         return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
169                 PendingIntent.FLAG_CANCEL_CURRENT);
170     }
171
172     /**
173      * @return {@link PendingIntent} for the primary {@link SliceAction}.
174      */
175     public static PendingIntent getContentPendingIntent(Context context, SliceData sliceData) {
176         final Intent intent = getContentIntent(context, sliceData);
177         return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
178     }
179
180     /**
181      * @return {@link PendingIntent} to the Settings home page.
182      */
183     public static PendingIntent getSettingsIntent(Context context) {
184         final Intent intent = new Intent(Settings.ACTION_SETTINGS);
185         return PendingIntent.getActivity(context, 0 /* requestCode */, intent, 0 /* flags */);
186     }
187
188     /**
189      * @return the summary text for a {@link Slice} built for {@param sliceData}.
190      */
191     public static CharSequence getSubtitleText(Context context,
192             AbstractPreferenceController controller, SliceData sliceData) {
193         CharSequence summaryText;
194         if (controller != null) {
195             summaryText = controller.getSummary();
196
197             if (isValidSummary(context, summaryText)) {
198                 return summaryText;
199             }
200         }
201
202         summaryText = sliceData.getSummary();
203         if (isValidSummary(context, summaryText)) {
204             return summaryText;
205         }
206
207         return "";
208     }
209
210     public static Uri getUri(String path, boolean isPlatformSlice) {
211         final String authority = isPlatformSlice
212                 ? SettingsSlicesContract.AUTHORITY
213                 : SettingsSliceProvider.SLICE_AUTHORITY;
214         return new Uri.Builder()
215                 .scheme(ContentResolver.SCHEME_CONTENT)
216                 .authority(authority)
217                 .appendPath(path)
218                 .build();
219     }
220
221     @VisibleForTesting
222     static Intent getContentIntent(Context context, SliceData sliceData) {
223         final Uri contentUri = new Uri.Builder().appendPath(sliceData.getKey()).build();
224         final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
225                 sliceData.getFragmentClassName(), sliceData.getKey(),
226                 sliceData.getScreenTitle().toString(), 0 /* TODO */);
227         intent.setClassName(context.getPackageName(), SubSettings.class.getName());
228         intent.setData(contentUri);
229         return intent;
230     }
231
232     private static Slice buildToggleSlice(Context context, SliceData sliceData,
233             BasePreferenceController controller) {
234         final PendingIntent contentIntent = getContentPendingIntent(context, sliceData);
235         final IconCompat icon = IconCompat.createWithResource(context, sliceData.getIconResource());
236         final CharSequence subtitleText = getSubtitleText(context, controller, sliceData);
237         final TogglePreferenceController toggleController =
238                 (TogglePreferenceController) controller;
239         final SliceAction sliceAction = getToggleAction(context, sliceData,
240                 toggleController.isChecked());
241         final List<String> keywords = buildSliceKeywords(sliceData);
242
243         return new ListBuilder(context, sliceData.getUri(), SLICE_TTL_MILLIS)
244                 .addRow(rowBuilder -> rowBuilder
245                         .setTitle(sliceData.getTitle())
246                         .setSubtitle(subtitleText)
247                         .setPrimaryAction(
248                                 new SliceAction(contentIntent, icon, sliceData.getTitle()))
249                         .addEndItem(sliceAction))
250                 .setKeywords(keywords)
251                 .build();
252     }
253
254     private static Slice buildIntentSlice(Context context, SliceData sliceData,
255             BasePreferenceController controller) {
256         final PendingIntent contentIntent = getContentPendingIntent(context, sliceData);
257         final IconCompat icon = IconCompat.createWithResource(context, sliceData.getIconResource());
258         final CharSequence subtitleText = getSubtitleText(context, controller, sliceData);
259         final List<String> keywords = buildSliceKeywords(sliceData);
260
261         return new ListBuilder(context, sliceData.getUri(), SLICE_TTL_MILLIS)
262                 .addRow(rowBuilder -> rowBuilder
263                         .setTitle(sliceData.getTitle())
264                         .setSubtitle(subtitleText)
265                         .setPrimaryAction(
266                                 new SliceAction(contentIntent, icon, sliceData.getTitle())))
267                 .setKeywords(keywords)
268                 .build();
269     }
270
271     private static Slice buildSliderSlice(Context context, SliceData sliceData,
272             BasePreferenceController controller) {
273         final SliderPreferenceController sliderController = (SliderPreferenceController) controller;
274         final PendingIntent actionIntent = getSliderAction(context, sliceData);
275         final PendingIntent contentIntent = getContentPendingIntent(context, sliceData);
276         final IconCompat icon = IconCompat.createWithResource(context, sliceData.getIconResource());
277         final SliceAction primaryAction = new SliceAction(contentIntent, icon,
278                 sliceData.getTitle());
279         final List<String> keywords = buildSliceKeywords(sliceData);
280
281         return new ListBuilder(context, sliceData.getUri(), SLICE_TTL_MILLIS)
282                 .addInputRange(builder -> builder
283                         .setTitle(sliceData.getTitle())
284                         .setMax(sliderController.getMaxSteps())
285                         .setValue(sliderController.getSliderPosition())
286                         .setInputAction(actionIntent)
287                         .setPrimaryAction(primaryAction))
288                 .setKeywords(keywords)
289                 .build();
290     }
291
292     private static BasePreferenceController getPreferenceController(Context context,
293             String controllerClassName, String controllerKey) {
294         try {
295             return BasePreferenceController.createInstance(context, controllerClassName);
296         } catch (IllegalStateException e) {
297             // Do nothing
298         }
299
300         return BasePreferenceController.createInstance(context, controllerClassName, controllerKey);
301     }
302
303     private static SliceAction getToggleAction(Context context, SliceData sliceData,
304             boolean isChecked) {
305         PendingIntent actionIntent = getActionIntent(context,
306                 SettingsSliceProvider.ACTION_TOGGLE_CHANGED, sliceData);
307         return new SliceAction(actionIntent, null, isChecked);
308     }
309
310     private static PendingIntent getSliderAction(Context context, SliceData sliceData) {
311         return getActionIntent(context, SettingsSliceProvider.ACTION_SLIDER_CHANGED, sliceData);
312     }
313
314     private static boolean isValidSummary(Context context, CharSequence summary) {
315         if (summary == null || TextUtils.isEmpty(summary.toString().trim())) {
316             return false;
317         }
318
319         final CharSequence placeHolder = context.getText(R.string.summary_placeholder);
320         final CharSequence doublePlaceHolder =
321                 context.getText(R.string.summary_two_lines_placeholder);
322
323         return !(TextUtils.equals(summary, placeHolder)
324                 || TextUtils.equals(summary, doublePlaceHolder));
325     }
326
327     private static List<String> buildSliceKeywords(SliceData data) {
328         final List<String> keywords = new ArrayList<>();
329
330         keywords.add(data.getTitle());
331
332         if (!TextUtils.equals(data.getTitle(), data.getScreenTitle())) {
333             keywords.add(data.getScreenTitle().toString());
334         }
335
336         final String keywordString = data.getKeywords();
337         if (keywordString != null) {
338             final String[] keywordArray = keywordString.split(",");
339             keywords.addAll(Arrays.asList(keywordArray));
340         }
341
342         return keywords;
343     }
344
345     private static Slice buildUnavailableSlice(Context context, SliceData data,
346             BasePreferenceController controller) {
347         final String title = data.getTitle();
348         final List<String> keywords = buildSliceKeywords(data);
349         final String summary;
350         final SliceAction primaryAction;
351         final IconCompat icon = IconCompat.createWithResource(context, data.getIconResource());
352
353         switch (controller.getAvailabilityStatus()) {
354             case UNSUPPORTED_ON_DEVICE:
355                 summary = context.getString(R.string.unsupported_setting_summary);
356                 primaryAction = new SliceAction(getSettingsIntent(context), icon, title);
357                 break;
358             case DISABLED_FOR_USER:
359                 summary = context.getString(R.string.disabled_for_user_setting_summary);
360                 primaryAction = new SliceAction(getContentPendingIntent(context, data), icon,
361                         title);
362                 break;
363             case DISABLED_DEPENDENT_SETTING:
364                 summary = context.getString(R.string.disabled_dependent_setting_summary);
365                 primaryAction = new SliceAction(getContentPendingIntent(context, data), icon,
366                         title);
367                 break;
368             case CONDITIONALLY_UNAVAILABLE:
369             default:
370                 summary = context.getString(R.string.unknown_unavailability_setting_summary);
371                 primaryAction = new SliceAction(getSettingsIntent(context), icon, title);
372         }
373
374         return new ListBuilder(context, data.getUri(), SLICE_TTL_MILLIS)
375                 .addRow(builder -> builder
376                         .setTitle(title)
377                         .setSubtitle(summary)
378                         .setPrimaryAction(primaryAction))
379                 .setKeywords(keywords)
380                 .build();
381     }
382 }