1 /* 2 * Copyright (C) 2018 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.shortcut; 18 19 import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE; 20 21 import android.app.Activity; 22 import android.app.settings.SettingsEnums; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ResolveInfo; 28 import android.content.pm.ShortcutInfo; 29 import android.content.pm.ShortcutManager; 30 import android.net.ConnectivityManager; 31 import android.util.Log; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.preference.Preference; 35 import androidx.preference.PreferenceCategory; 36 import androidx.preference.PreferenceGroup; 37 38 import com.android.settings.R; 39 import com.android.settings.Settings; 40 import com.android.settings.Settings.DataUsageSummaryActivity; 41 import com.android.settings.Settings.TetherSettingsActivity; 42 import com.android.settings.Settings.WifiTetherSettingsActivity; 43 import com.android.settings.core.BasePreferenceController; 44 import com.android.settings.gestures.OneHandedSettingsUtils; 45 import com.android.settings.network.SubscriptionUtil; 46 import com.android.settings.network.telephony.MobileNetworkUtils; 47 import com.android.settings.overlay.FeatureFactory; 48 import com.android.settings.wifi.WifiUtils; 49 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 50 51 import java.util.ArrayList; 52 import java.util.Collections; 53 import java.util.Comparator; 54 import java.util.List; 55 56 /** 57 * {@link BasePreferenceController} that populates a list of widgets that Settings app support. 58 */ 59 public class CreateShortcutPreferenceController extends BasePreferenceController { 60 61 private static final String TAG = "CreateShortcutPrefCtrl"; 62 63 private final ShortcutManager mShortcutManager; 64 private final PackageManager mPackageManager; 65 private final ConnectivityManager mConnectivityManager; 66 private final MetricsFeatureProvider mMetricsFeatureProvider; 67 private Activity mHost; 68 CreateShortcutPreferenceController(Context context, String preferenceKey)69 public CreateShortcutPreferenceController(Context context, String preferenceKey) { 70 super(context, preferenceKey); 71 mConnectivityManager = 72 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 73 mShortcutManager = context.getSystemService(ShortcutManager.class); 74 mPackageManager = context.getPackageManager(); 75 mMetricsFeatureProvider = FeatureFactory.getFeatureFactory() 76 .getMetricsFeatureProvider(); 77 } 78 setActivity(Activity host)79 public void setActivity(Activity host) { 80 mHost = host; 81 } 82 83 @Override getAvailabilityStatus()84 public int getAvailabilityStatus() { 85 return AVAILABLE_UNSEARCHABLE; 86 } 87 88 @Override updateState(Preference preference)89 public void updateState(Preference preference) { 90 if (!(preference instanceof PreferenceGroup)) { 91 return; 92 } 93 final PreferenceGroup group = (PreferenceGroup) preference; 94 group.removeAll(); 95 final List<ResolveInfo> shortcuts = queryShortcuts(); 96 final Context uiContext = preference.getContext(); 97 if (shortcuts.isEmpty()) { 98 return; 99 } 100 PreferenceCategory category = new PreferenceCategory(uiContext); 101 group.addPreference(category); 102 int bucket = 0; 103 for (ResolveInfo info : shortcuts) { 104 // Priority is not consecutive (aka, jumped), add a divider between prefs. 105 final int currentBucket = info.priority / 10; 106 boolean needDivider = currentBucket != bucket; 107 bucket = currentBucket; 108 if (needDivider) { 109 // add a new Category 110 category = new PreferenceCategory(uiContext); 111 group.addPreference(category); 112 } 113 114 final Preference pref = new Preference(uiContext); 115 pref.setTitle(info.loadLabel(mPackageManager)); 116 pref.setKey(info.activityInfo.getComponentName().flattenToString()); 117 pref.setOnPreferenceClickListener(clickTarget -> { 118 if (mHost == null) { 119 return false; 120 } 121 final Intent shortcutIntent = createResultIntent(info); 122 mHost.setResult(Activity.RESULT_OK, shortcutIntent); 123 logCreateShortcut(info); 124 mHost.finish(); 125 return true; 126 }); 127 category.addPreference(pref); 128 } 129 } 130 131 /** 132 * Create {@link Intent} that will be consumed by ShortcutManager, which later generates a 133 * launcher widget using this intent. 134 */ 135 @VisibleForTesting createResultIntent(ResolveInfo resolveInfo)136 Intent createResultIntent(ResolveInfo resolveInfo) { 137 ShortcutInfo info = Shortcuts.createShortcutInfo(mContext, resolveInfo); 138 Intent intent = mShortcutManager.createShortcutResultIntent(info); 139 if (intent == null) { 140 intent = new Intent(); 141 } 142 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 143 Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings)) 144 .putExtra(Intent.EXTRA_SHORTCUT_INTENT, info.getIntent()) 145 .putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getShortLabel()); 146 147 final ActivityInfo activityInfo = resolveInfo.activityInfo; 148 if (activityInfo.icon != 0) { 149 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, Shortcuts.createIcon( 150 mContext, 151 activityInfo.applicationInfo, 152 activityInfo.icon, 153 R.layout.shortcut_badge, 154 mContext.getResources().getDimensionPixelSize(R.dimen.shortcut_size))); 155 } 156 return intent; 157 } 158 159 /** 160 * Finds all shortcut supported by Settings. 161 */ 162 @VisibleForTesting queryShortcuts()163 List<ResolveInfo> queryShortcuts() { 164 final List<ResolveInfo> shortcuts = new ArrayList<>(); 165 final List<ResolveInfo> activities = mPackageManager.queryIntentActivities(SHORTCUT_PROBE, 166 PackageManager.GET_META_DATA); 167 168 if (activities == null) { 169 return null; 170 } 171 for (ResolveInfo info : activities) { 172 if (info.activityInfo.name.contains( 173 Settings.OneHandedSettingsActivity.class.getSimpleName())) { 174 if (!OneHandedSettingsUtils.isSupportOneHandedMode()) { 175 continue; 176 } 177 } 178 if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) { 179 if (!mConnectivityManager.isTetheringSupported()) { 180 continue; 181 } 182 } 183 if (info.activityInfo.name.endsWith(WifiTetherSettingsActivity.class.getSimpleName())) { 184 if (!canShowWifiHotspot()) { 185 Log.d(TAG, "Skipping Wi-Fi hotspot settings:" + info.activityInfo); 186 continue; 187 } 188 } 189 if (!info.activityInfo.applicationInfo.isSystemApp()) { 190 Log.d(TAG, "Skipping non-system app: " + info.activityInfo); 191 continue; 192 } 193 if (info.activityInfo.name.endsWith(DataUsageSummaryActivity.class.getSimpleName())) { 194 if (!canShowDataUsage()) { 195 Log.d(TAG, "Skipping data usage settings:" + info.activityInfo); 196 continue; 197 } 198 } 199 shortcuts.add(info); 200 } 201 Collections.sort(shortcuts, SHORTCUT_COMPARATOR); 202 return shortcuts; 203 } 204 205 @VisibleForTesting canShowDataUsage()206 boolean canShowDataUsage() { 207 return SubscriptionUtil.isSimHardwareVisible(mContext) 208 && !MobileNetworkUtils.isMobileNetworkUserRestricted(mContext); 209 } 210 211 @VisibleForTesting canShowWifiHotspot()212 boolean canShowWifiHotspot() { 213 return WifiUtils.canShowWifiHotspot(mContext); 214 } 215 logCreateShortcut(ResolveInfo info)216 private void logCreateShortcut(ResolveInfo info) { 217 if (info == null || info.activityInfo == null) { 218 return; 219 } 220 mMetricsFeatureProvider.action( 221 mContext, SettingsEnums.ACTION_SETTINGS_CREATE_SHORTCUT, 222 info.activityInfo.name); 223 } 224 225 private static final Comparator<ResolveInfo> SHORTCUT_COMPARATOR = 226 (i1, i2) -> i1.priority - i2.priority; 227 } 228