1 /*
2  * Copyright (C) 2022 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.display;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.annotation.Nullable;
29 import androidx.annotation.VisibleForTesting;
30 
31 import com.android.settings.R;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /** Utilities for display settings related to customizable lock screen features. */
37 public final class CustomizableLockScreenUtils {
38 
39     private static final String TAG = "CustomizableLockScreenUtils";
40     private static final Uri BASE_URI = new Uri.Builder()
41             .scheme(ContentResolver.SCHEME_CONTENT)
42             .authority("com.android.systemui.customization")
43             .build();
44     @VisibleForTesting
45     static final Uri FLAGS_URI = BASE_URI.buildUpon()
46             .path("flags")
47             .build();
48     @VisibleForTesting
49     static final Uri SELECTIONS_URI = BASE_URI.buildUpon()
50             .appendPath("lockscreen_quickaffordance")
51             .appendPath("selections")
52             .build();
53     @VisibleForTesting
54     static final String NAME = "name";
55     @VisibleForTesting
56     static final String VALUE = "value";
57     @VisibleForTesting
58     static final String ENABLED_FLAG =
59             "is_custom_lock_screen_quick_affordances_feature_enabled";
60     @VisibleForTesting
61     static final String AFFORDANCE_NAME = "affordance_name";
62 
63     @VisibleForTesting
64     static final String WALLPAPER_LAUNCH_SOURCE = "com.android.wallpaper.LAUNCH_SOURCE";
65     @VisibleForTesting
66     static final String LAUNCH_SOURCE_SETTINGS = "app_launched_settings";
67 
68 
CustomizableLockScreenUtils()69     private CustomizableLockScreenUtils() {}
70 
71     /**
72      * Queries and returns whether the customizable lock screen quick affordances feature is enabled
73      * on the device.
74      *
75      * <p>This is a slow, blocking call that shouldn't be made on the main thread.
76      */
isFeatureEnabled(Context context)77     public static boolean isFeatureEnabled(Context context) {
78         if (!isWallpaperPickerInstalled(context)) {
79             return false;
80         }
81 
82         try (Cursor cursor = context.getContentResolver().query(
83                 FLAGS_URI,
84                 null,
85                 null,
86                 null)) {
87             if (cursor == null) {
88                 Log.w(TAG, "Cursor was null!");
89                 return false;
90             }
91 
92             final int indexOfNameColumn = cursor.getColumnIndex(NAME);
93             final int indexOfValueColumn = cursor.getColumnIndex(VALUE);
94             if (indexOfNameColumn == -1 || indexOfValueColumn == -1) {
95                 Log.w(TAG, "Cursor doesn't contain " + NAME + " or " + VALUE + "!");
96                 return false;
97             }
98 
99             while (cursor.moveToNext()) {
100                 final String name = cursor.getString(indexOfNameColumn);
101                 final int value = cursor.getInt(indexOfValueColumn);
102                 if (TextUtils.equals(ENABLED_FLAG, name)) {
103                     Log.d(TAG, ENABLED_FLAG + "=" + value);
104                     return value == 1;
105                 }
106             }
107 
108             Log.w(TAG, "Flag with name \"" + ENABLED_FLAG + "\" not found!");
109             return false;
110         } catch (Exception e) {
111             Log.e(TAG, "Exception while querying quick affordance content provider", e);
112             return false;
113         }
114     }
115 
116     /**
117      * Queries and returns a summary text for the currently-selected lock screen quick affordances.
118      *
119      * <p>This is a slow, blocking call that shouldn't be made on the main thread.
120      */
121     @Nullable
getQuickAffordanceSummary(Context context)122     public static CharSequence getQuickAffordanceSummary(Context context) {
123         try (Cursor cursor = context.getContentResolver().query(
124                 SELECTIONS_URI,
125                 null,
126                 null,
127                 null)) {
128             if (cursor == null) {
129                 Log.w(TAG, "Cursor was null!");
130                 return null;
131             }
132 
133             final int columnIndex = cursor.getColumnIndex(AFFORDANCE_NAME);
134             if (columnIndex == -1) {
135                 Log.w(TAG, "Cursor doesn't contain \"" + AFFORDANCE_NAME + "\" column!");
136                 return null;
137             }
138 
139             final List<String> affordanceNames = new ArrayList<>(cursor.getCount());
140             while (cursor.moveToNext()) {
141                 final String affordanceName = cursor.getString(columnIndex);
142                 if (!TextUtils.isEmpty(affordanceName)) {
143                     affordanceNames.add(affordanceName);
144                 }
145             }
146 
147             // We don't display more than the first two items.
148             final int usableAffordanceNameCount = Math.min(2, affordanceNames.size());
149             final List<String> arguments = new ArrayList<>(usableAffordanceNameCount);
150             if (!affordanceNames.isEmpty()) {
151                 arguments.add(affordanceNames.get(0));
152             }
153             if (affordanceNames.size() > 1) {
154                 arguments.add(affordanceNames.get(1));
155             }
156 
157             return context.getResources().getQuantityString(
158                     R.plurals.lockscreen_quick_affordances_summary,
159                     usableAffordanceNameCount,
160                     arguments.toArray());
161         } catch (Exception e) {
162             Log.e(TAG, "Exception while querying quick affordance content provider", e);
163             return null;
164         }
165     }
166 
167     /**
168      * Returns a new {@link Intent} that can be used to start the wallpaper picker
169      * activity.
170      */
newIntent()171     public static Intent newIntent() {
172         final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
173         // By adding the launch source here, we tell our destination (in this case, the wallpaper
174         // picker app) that it's been launched from within settings. That way, if we are in a
175         // multi-pane configuration (for example, for large screens), the wallpaper picker app can
176         // safely skip redirecting to the multi-pane version of its activity, as it's already opened
177         // within a multi-pane configuration context.
178         intent.putExtra(WALLPAPER_LAUNCH_SOURCE, LAUNCH_SOURCE_SETTINGS);
179         return intent;
180     }
181 
isWallpaperPickerInstalled(Context context)182     private static boolean isWallpaperPickerInstalled(Context context) {
183         final PackageManager packageManager = context.getPackageManager();
184         return newIntent().resolveActivity(packageManager) != null;
185     }
186 }
187