diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d6fcf902026..3b6d4f8cd6f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -148,12 +148,15 @@ android:taskAffinity="com.android.settings" android:parentActivityName="Settings" /> - - - + + + @@ -241,10 +244,8 @@ - + - - - + - - + - - + - - - + - - + - - @@ -901,15 +895,6 @@ android:resource="@string/display_dashboard_summary"/> - - - - - - + - @@ -962,10 +946,8 @@ - + - - @@ -1006,10 +988,8 @@ - + - - - + - - + - - - + - - @@ -1749,11 +1724,9 @@ - + - - @@ -2153,9 +2126,8 @@ - + - @@ -2307,9 +2279,8 @@ - + - @@ -2497,9 +2468,8 @@ - + - @@ -2515,7 +2485,6 @@ android:value="true" /> - - + - - @@ -2983,7 +2950,7 @@ - + @@ -3004,7 +2971,7 @@ - + diff --git a/res/layout/activity_list.xml b/res/layout/activity_list.xml deleted file mode 100644 index ca9e12b7fb3..00000000000 --- a/res/layout/activity_list.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - diff --git a/res/values/strings.xml b/res/values/strings.xml index d0c076ecdfd..d799ea544bd 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -689,8 +689,6 @@ Settings Settings shortcut - - No matching activities found. Airplane mode @@ -2502,11 +2500,11 @@ Boosted Saturated - - Automatic + + Adaptive Use accurate colors only - + Adjust between vivid and accurate colors diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index 060868e37fc..bef13516968 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -14,7 +14,9 @@ limitations under the License. --> - @@ -136,6 +138,7 @@ android:title="@string/accessibility_display_inversion_preference_title" android:summary="@string/accessibility_display_inversion_preference_subtitle" android:persistent="false" - android:icon="@drawable/ic_color_inversion"/> + android:icon="@drawable/ic_color_inversion" + settings:controller="com.android.settings.accessibility.ColorInversionPreferenceController"/> diff --git a/res/xml/create_shortcut.xml b/res/xml/create_shortcut.xml new file mode 100644 index 00000000000..15b38358920 --- /dev/null +++ b/res/xml/create_shortcut.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 00501272cf0..99d7d474b0a 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -30,6 +30,7 @@ public class Settings extends SettingsActivity { */ public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */} public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ } + public static class CreateShortcutActivity extends SettingsActivity { /* empty */ } public static class SimSettingsActivity extends SettingsActivity { /* empty */ } public static class TetherSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiTetherSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/SettingsInitialize.java b/src/com/android/settings/SettingsInitialize.java index b4b6d731991..a66207e8127 100644 --- a/src/com/android/settings/SettingsInitialize.java +++ b/src/com/android/settings/SettingsInitialize.java @@ -34,7 +34,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Log; -import com.android.settings.shortcut.CreateShortcut; +import com.android.settings.Settings.CreateShortcutActivity; import java.util.ArrayList; import java.util.List; @@ -101,7 +101,8 @@ public class SettingsInitialize extends BroadcastReceiver { pm.setComponentEnabledSetting(settingsComponentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); // Disable shortcut picker. - ComponentName shortcutComponentName = new ComponentName(context, CreateShortcut.class); + ComponentName shortcutComponentName = new ComponentName( + context, CreateShortcutActivity.class); pm.setComponentEnabledSetting(shortcutComponentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 3464fd976ad..61f2672b848 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -224,6 +224,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private Preference mDisplayDaltonizerPreferenceScreen; private Preference mVibrationPreferenceScreen; private SwitchPreference mToggleInversionPreference; + private ColorInversionPreferenceController mInversionPreferenceController; private int mLongPressTimeoutDefault; @@ -304,9 +305,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements if (mSelectLongPressTimeoutPreference == preference) { handleLongPressTimeoutPreferenceChange((String) newValue); return true; - } else if (mToggleInversionPreference == preference) { - handleToggleInversionPreferenceChange((Boolean) newValue); - return true; } return false; } @@ -318,11 +316,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mLongPressTimeoutValueToTitleMap.get(stringValue)); } - private void handleToggleInversionPreferenceChange(boolean checked) { - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (checked ? 1 : 0)); - } - @Override public boolean onPreferenceTreeClick(Preference preference) { if (mToggleHighTextContrastPreference == preference) { @@ -412,7 +405,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements // Display inversion. mToggleInversionPreference = (SwitchPreference) findPreference(TOGGLE_INVERSION_PREFERENCE); - mToggleInversionPreference.setOnPreferenceChangeListener(this); + mInversionPreferenceController = + new ColorInversionPreferenceController(getContext(), TOGGLE_INVERSION_PREFERENCE); + mInversionPreferenceController.displayPreference(getPreferenceScreen()); // Power button ends calls. mTogglePowerButtonEndsCallPreference = @@ -652,8 +647,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1); // If the quick setting is enabled, the preference MUST be enabled. - mToggleInversionPreference.setChecked(Settings.Secure.getInt(getContentResolver(), - Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1); + mInversionPreferenceController.updateState(mToggleInversionPreference); // Power button ends calls. if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER) diff --git a/src/com/android/settings/accessibility/ColorInversionPreferenceController.java b/src/com/android/settings/accessibility/ColorInversionPreferenceController.java new file mode 100644 index 00000000000..091f109acb5 --- /dev/null +++ b/src/com/android/settings/accessibility/ColorInversionPreferenceController.java @@ -0,0 +1,54 @@ +/* + * 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.accessibility; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.core.TogglePreferenceController; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +public class ColorInversionPreferenceController extends TogglePreferenceController { + @VisibleForTesting + static final int ON = 1; + @VisibleForTesting + static final int OFF = 0; + + public ColorInversionPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, OFF) == ON; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (isChecked ? ON : OFF)); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } +} diff --git a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java index 0234647a446..eff97c3c900 100644 --- a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java +++ b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java @@ -59,7 +59,9 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc super.displayPreference(screen); if (isAvailable()) { mPreference = screen.findPreference(getPreferenceKey()); - mBluetoothDeviceUpdater.setPrefContext(screen.getContext()); + final Context context = screen.getContext(); + mBluetoothDeviceUpdater.setPrefContext(context); + mSavedDockUpdater.setPreferenceContext(context); } } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index b486174c55d..64afd942406 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -115,6 +115,7 @@ import com.android.settings.print.PrintSettingsFragment; import com.android.settings.security.CryptKeeperSettings; import com.android.settings.security.LockscreenDashboardFragment; import com.android.settings.security.SecuritySettings; +import com.android.settings.shortcut.CreateShortcut; import com.android.settings.sim.SimSettings; import com.android.settings.support.SupportDashboardActivity; import com.android.settings.system.ResetDashboardFragment; @@ -142,6 +143,7 @@ public class SettingsGateway { */ public static final String[] ENTRY_FRAGMENTS = { AdvancedConnectedDeviceDashboardFragment.class.getName(), + CreateShortcut.class.getName(), WifiSettings.class.getName(), ConfigureWifiSettings.class.getName(), SavedAccessPointsWifiSettings.class.getName(), diff --git a/src/com/android/settings/shortcut/CreateShortcut.java b/src/com/android/settings/shortcut/CreateShortcut.java index 15378fd6bc8..b5b6438ed56 100644 --- a/src/com/android/settings/shortcut/CreateShortcut.java +++ b/src/com/android/settings/shortcut/CreateShortcut.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -16,179 +16,57 @@ package com.android.settings.shortcut; -import android.app.LauncherActivity; -import android.content.ComponentName; +import static com.android.settings.search.actionbar.SearchMenuController + .NEED_SEARCH_ICON_IN_ACTION_BAR; + import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.graphics.drawable.LayerDrawable; -import android.net.ConnectivityManager; -import android.os.AsyncTask; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.MeasureSpec; -import android.widget.ImageView; -import android.widget.ListView; +import android.os.Bundle; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; -import com.android.settings.Settings.TetherSettingsActivity; -import com.android.settings.overlay.FeatureFactory; +import com.android.settings.dashboard.DashboardFragment; -import java.util.ArrayList; -import java.util.List; - -import androidx.annotation.VisibleForTesting; - -public class CreateShortcut extends LauncherActivity { +/** + * UI for create widget/shortcut screen. + */ +public class CreateShortcut extends DashboardFragment { private static final String TAG = "CreateShortcut"; - @VisibleForTesting - static final String SHORTCUT_ID_PREFIX = "component-shortcut-"; @Override - protected Intent getTargetIntent() { - return getBaseIntent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Bundle args = getArguments(); + if (args == null) { + args = new Bundle(); + setArguments(args); + } + args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false); } @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - final ListItem item = itemForPosition(position); - logCreateShortcut(item.resolveInfo); - setResult(RESULT_OK, createResultIntent(intentForPosition(position), - item.resolveInfo, item.label)); - finish(); - } - - @VisibleForTesting - Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo, - CharSequence label) { - shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - ShortcutManager sm = getSystemService(ShortcutManager.class); - ActivityInfo activityInfo = resolveInfo.activityInfo; - - Icon maskableIcon = activityInfo.icon != 0 && activityInfo.applicationInfo != null - ? Icon.createWithAdaptiveBitmap( - createIcon(activityInfo.applicationInfo, activityInfo.icon, - R.layout.shortcut_badge_maskable, - getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))) - : Icon.createWithResource(this, R.drawable.ic_launcher_settings); - String shortcutId = SHORTCUT_ID_PREFIX + - shortcutIntent.getComponent().flattenToShortString(); - ShortcutInfo info = new ShortcutInfo.Builder(this, shortcutId) - .setShortLabel(label) - .setIntent(shortcutIntent) - .setIcon(maskableIcon) - .build(); - Intent intent = sm.createShortcutResultIntent(info); - if (intent == null) { - intent = new Intent(); - } - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_settings)); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); - - if (activityInfo.icon != 0) { - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon( - activityInfo.applicationInfo, - activityInfo.icon, - R.layout.shortcut_badge, - getResources().getDimensionPixelSize(R.dimen.shortcut_size))); - } - return intent; - } - - private void logCreateShortcut(ResolveInfo info) { - if (info == null || info.activityInfo == null) { - return; - } - FeatureFactory.getFactory(this).getMetricsFeatureProvider().action( - this, MetricsProto.MetricsEvent.ACTION_SETTINGS_CREATE_SHORTCUT, - info.activityInfo.name); - } - - private Bitmap createIcon(ApplicationInfo app, int resource, int layoutRes, int size) { - final Context context = new ContextThemeWrapper(this, android.R.style.Theme_Material); - final View view = LayoutInflater.from(context).inflate(layoutRes, null); - final int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); - view.measure(spec, spec); - final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), - Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - - Drawable iconDrawable = null; - try { - iconDrawable = - getPackageManager().getResourcesForApplication(app).getDrawable(resource); - if (iconDrawable instanceof LayerDrawable) { - iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); - } - ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon"); - Icon icon = Icon.createWithResource(this, R.drawable.ic_launcher_settings); - ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon); - } - - view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); - view.draw(canvas); - return bitmap; + public void onAttach(Context context) { + super.onAttach(context); + use(CreateShortcutPreferenceController.class).setActivity(getActivity()); } @Override - protected boolean onEvaluateShowIcons() { - return false; + protected int getPreferenceScreenResId() { + return R.xml.create_shortcut; } @Override - protected void onSetContentView() { - setContentView(R.layout.activity_list); + protected String getLogTag() { + return TAG; } - /** - * Perform query on package manager for list items. The default - * implementation queries for activities. - */ @Override - protected List onQueryPackageManager(Intent queryIntent) { - List activities = getPackageManager().queryIntentActivities(queryIntent, - PackageManager.GET_META_DATA); - final ConnectivityManager cm = - (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - if (activities == null) { - return null; - } - for (int i = activities.size() - 1; i >= 0; i--) { - ResolveInfo info = activities.get(i); - if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) { - if (!cm.isTetheringSupported()) { - activities.remove(i); - } - } - if (!info.activityInfo.applicationInfo.isSystemApp()) { - Log.d(TAG, "Skipping non-system app: " + info.activityInfo); - activities.remove(i); - } - } - return activities; + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.SETTINGS_CREATE_SHORTCUT; } - @VisibleForTesting - static Intent getBaseIntent() { - return new Intent(Intent.ACTION_MAIN).addCategory("com.android.settings.SHORTCUT"); + @Override + public int getHelpResource() { + return 0; } - } diff --git a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java new file mode 100644 index 00000000000..4848bf6b049 --- /dev/null +++ b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java @@ -0,0 +1,261 @@ +/* + * 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.shortcut; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; +import android.net.ConnectivityManager; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.Settings.TetherSettingsActivity; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; + +/** + * {@link BasePreferenceController} that populates a list of widgets that Settings app support. + */ +public class CreateShortcutPreferenceController extends BasePreferenceController { + + private static final String TAG = "CreateShortcutPrefCtrl"; + + static final String SHORTCUT_ID_PREFIX = "component-shortcut-"; + static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN) + .addCategory("com.android.settings.SHORTCUT") + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + private final ShortcutManager mShortcutManager; + private final PackageManager mPackageManager; + private final ConnectivityManager mConnectivityManager; + private final MetricsFeatureProvider mMetricsFeatureProvider; + private Activity mHost; + + public CreateShortcutPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mConnectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mShortcutManager = context.getSystemService(ShortcutManager.class); + mPackageManager = context.getPackageManager(); + mMetricsFeatureProvider = FeatureFactory.getFactory(context) + .getMetricsFeatureProvider(); + } + + public void setActivity(Activity host) { + mHost = host; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public void updateState(Preference preference) { + if (!(preference instanceof PreferenceGroup)) { + return; + } + final PreferenceGroup group = (PreferenceGroup) preference; + group.removeAll(); + final List shortcuts = queryShortcuts(); + final Context uiContext = preference.getContext(); + if (shortcuts.isEmpty()) { + return; + } + PreferenceCategory category = new PreferenceCategory(uiContext); + group.addPreference(category); + int bucket = 0; + for (ResolveInfo info : shortcuts) { + // Priority is not consecutive (aka, jumped), add a divider between prefs. + final int currentBucket = info.priority / 10; + boolean needDivider = currentBucket != bucket; + bucket = currentBucket; + if (needDivider) { + // add a new Category + category = new PreferenceCategory(uiContext); + group.addPreference(category); + } + + final Preference pref = new Preference(uiContext); + pref.setTitle(info.loadLabel(mPackageManager)); + pref.setKey(info.activityInfo.getComponentName().flattenToString()); + pref.setOnPreferenceClickListener(clickTarget -> { + if (mHost == null) { + return false; + } + final Intent shortcutIntent = createResultIntent( + buildShortcutIntent(info), + info, clickTarget.getTitle()); + mHost.setResult(Activity.RESULT_OK, shortcutIntent); + logCreateShortcut(info); + mHost.finish(); + return true; + }); + category.addPreference(pref); + } + } + + /** + * Create {@link Intent} that will be consumed by ShortcutManager, which later generates a + * launcher widget using this intent. + */ + @VisibleForTesting + Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo, + CharSequence label) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + + final Icon maskableIcon; + if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) { + maskableIcon = Icon.createWithAdaptiveBitmap(createIcon( + activityInfo.applicationInfo, activityInfo.icon, + R.layout.shortcut_badge_maskable, + mContext.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))); + } else { + maskableIcon = Icon.createWithResource(mContext, R.drawable.ic_launcher_settings); + } + final String shortcutId = SHORTCUT_ID_PREFIX + + shortcutIntent.getComponent().flattenToShortString(); + ShortcutInfo info = new ShortcutInfo.Builder(mContext, shortcutId) + .setShortLabel(label) + .setIntent(shortcutIntent) + .setIcon(maskableIcon) + .build(); + Intent intent = mShortcutManager.createShortcutResultIntent(info); + if (intent == null) { + intent = new Intent(); + } + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings)) + .putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent) + .putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + + if (activityInfo.icon != 0) { + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon( + activityInfo.applicationInfo, + activityInfo.icon, + R.layout.shortcut_badge, + mContext.getResources().getDimensionPixelSize(R.dimen.shortcut_size))); + } + return intent; + } + + /** + * Finds all shortcut supported by Settings. + */ + @VisibleForTesting + List queryShortcuts() { + final List shortcuts = new ArrayList<>(); + final List activities = mPackageManager.queryIntentActivities(SHORTCUT_PROBE, + PackageManager.GET_META_DATA); + + if (activities == null) { + return null; + } + for (ResolveInfo info : activities) { + if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) { + if (!mConnectivityManager.isTetheringSupported()) { + continue; + } + } + if (!info.activityInfo.applicationInfo.isSystemApp()) { + Log.d(TAG, "Skipping non-system app: " + info.activityInfo); + continue; + } + shortcuts.add(info); + } + Collections.sort(shortcuts, SHORTCUT_COMPARATOR); + return shortcuts; + } + + private void logCreateShortcut(ResolveInfo info) { + if (info == null || info.activityInfo == null) { + return; + } + mMetricsFeatureProvider.action( + mContext, MetricsProto.MetricsEvent.ACTION_SETTINGS_CREATE_SHORTCUT, + info.activityInfo.name); + } + + private Intent buildShortcutIntent(ResolveInfo info) { + return new Intent(SHORTCUT_PROBE) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP) + .setClassName(info.activityInfo.packageName, info.activityInfo.name); + } + + private Bitmap createIcon(ApplicationInfo app, int resource, int layoutRes, int size) { + final Context context = new ContextThemeWrapper(mContext, android.R.style.Theme_Material); + final View view = LayoutInflater.from(context).inflate(layoutRes, null); + final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY); + view.measure(spec, spec); + final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), + Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + Drawable iconDrawable; + try { + iconDrawable = mPackageManager.getResourcesForApplication(app).getDrawable(resource); + if (iconDrawable instanceof LayerDrawable) { + iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); + } + ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon"); + Icon icon = Icon.createWithResource(mContext, R.drawable.ic_launcher_settings); + ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon); + } + + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + view.draw(canvas); + return bitmap; + } + + private static final Comparator SHORTCUT_COMPARATOR = + new Comparator() { + + @Override + public int compare(ResolveInfo i1, ResolveInfo i2) { + return i1.priority - i2.priority; + } + }; +} diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java b/src/com/android/settings/shortcut/ShortcutsUpdateTask.java index 5144fe11f10..54f7d1ceafe 100644 --- a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java +++ b/src/com/android/settings/shortcut/ShortcutsUpdateTask.java @@ -16,8 +16,12 @@ package com.android.settings.shortcut; +import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX; +import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_PROBE; + import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; @@ -42,12 +46,12 @@ public class ShortcutsUpdateTask extends AsyncTask { List updates = new ArrayList<>(); for (ShortcutInfo info : sm.getPinnedShortcuts()) { - if (!info.getId().startsWith(CreateShortcut.SHORTCUT_ID_PREFIX)) { + if (!info.getId().startsWith(SHORTCUT_ID_PREFIX)) { continue; } ComponentName cn = ComponentName.unflattenFromString( - info.getId().substring(CreateShortcut.SHORTCUT_ID_PREFIX.length())); - ResolveInfo ri = pm.resolveActivity(CreateShortcut.getBaseIntent().setComponent(cn), 0); + info.getId().substring(SHORTCUT_ID_PREFIX.length())); + ResolveInfo ri = pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0); if (ri == null) { continue; } diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index a55f02483c8..6bbdca76733 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -5,6 +5,7 @@ com.android.settings.bluetooth.BluetoothDeviceDetailsFragment com.android.settings.bluetooth.BluetoothPairingDetail com.android.settings.accounts.AccountDetailDashboardFragment com.android.settings.accounts.ManagedProfileSettings +com.android.settings.shortcut.CreateShortcut com.android.settings.fuelgauge.PowerUsageAnomalyDetails com.android.settings.fuelgauge.AdvancedPowerUsageDetail com.android.settings.development.featureflags.FeatureFlagsDashboard diff --git a/tests/robotests/src/com/android/settings/accessibility/ColorInversionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ColorInversionPreferenceControllerTest.java new file mode 100644 index 00000000000..880cb9fa595 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ColorInversionPreferenceControllerTest.java @@ -0,0 +1,97 @@ +/* + * 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.accessibility; + +import static com.android.settings.accessibility.ColorInversionPreferenceController.OFF; +import static com.android.settings.accessibility.ColorInversionPreferenceController.ON; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.SwitchPreference; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ColorInversionPreferenceControllerTest { + private static final int UNKNOWN = -1; + private Context mContext; + private ColorInversionPreferenceController mController; + private SwitchPreference mPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new ColorInversionPreferenceController(mContext, "pref_key"); + mPreference = new SwitchPreference(mContext); + mController.updateState(mPreference); + } + + @Test + public void getAvailabilityStatus_available() { + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void isChecked_enabled() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, ON); + + mController.updateState(mPreference); + + assertThat(mController.isChecked()).isTrue(); + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void isChecked_disabled() { + Settings.System.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, OFF); + + mController.updateState(mPreference); + + assertThat(mController.isChecked()).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void setChecked_enabled() { + mController.setChecked(true); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, UNKNOWN)).isEqualTo(ON); + } + + @Test + public void setChecked_disabled() { + mController.setChecked(false); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, UNKNOWN)).isEqualTo(OFF); + } +} diff --git a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java similarity index 64% rename from tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java rename to tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java index 42190c712e2..6bccfe5f784 100644 --- a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java +++ b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java @@ -16,7 +16,7 @@ package com.android.settings.shortcut; -import static com.android.settings.shortcut.CreateShortcut.SHORTCUT_ID_PREFIX; +import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; @@ -26,6 +26,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -44,7 +45,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; @@ -54,41 +54,44 @@ import java.util.Arrays; import java.util.List; /** - * Tests for {@link CreateShortcutTest} + * Tests for {@link CreateShortcutPreferenceController} */ @RunWith(SettingsRobolectricTestRunner.class) @Config(shadows = ShadowConnectivityManager.class) -public class CreateShortcutTest { +public class CreateShortcutPreferenceControllerTest { + + @Mock + private ShortcutManager mShortcutManager; + @Mock + private Activity mHost; private Context mContext; private ShadowConnectivityManager mShadowConnectivityManager; private ShadowPackageManager mPackageManager; - - @Mock - private ShortcutManager mShortcutManager; + private CreateShortcutPreferenceController mController; @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); + doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE)); mPackageManager = Shadow.extract(mContext.getPackageManager()); mShadowConnectivityManager = ShadowConnectivityManager.getShadow(); mShadowConnectivityManager.setTetheringSupported(true); + + mController = new CreateShortcutPreferenceController(mContext, "key"); + mController.setActivity(mHost); } @Test public void createResultIntent() { - CreateShortcut orgActivity = Robolectric.setupActivity(CreateShortcut.class); - CreateShortcut activity = spy(orgActivity); - doReturn(mShortcutManager).when(activity).getSystemService(eq(Context.SHORTCUT_SERVICE)); - when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class))) .thenReturn(new Intent().putExtra("d1", "d2")); - final Intent intent = CreateShortcut.getBaseIntent() - .setClass(activity, Settings.ManageApplicationsActivity.class); - final ResolveInfo ri = activity.getPackageManager().resolveActivity(intent, 0); - final Intent result = activity.createResultIntent(intent, ri, "dummy"); + final Intent intent = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE) + .setClass(mContext, Settings.ManageApplicationsActivity.class); + final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0); + final Intent result = mController.createResultIntent(intent, ri, "dummy"); assertThat(result.getStringExtra("d1")).isEqualTo("d2"); assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull(); @@ -101,7 +104,7 @@ public class CreateShortcutTest { } @Test - public void queryActivities_shouldOnlyIncludeSystemApp() { + public void queryShortcuts_shouldOnlyIncludeSystemApp() { final ResolveInfo ri1 = new ResolveInfo(); ri1.activityInfo = new ActivityInfo(); ri1.activityInfo.name = "activity1"; @@ -113,18 +116,38 @@ public class CreateShortcutTest { ri2.activityInfo.applicationInfo = new ApplicationInfo(); ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; - mPackageManager.addResolveInfoForIntent(CreateShortcut.getBaseIntent(), + mPackageManager.addResolveInfoForIntent( + new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE), Arrays.asList(ri1, ri2)); - TestClass orgActivity = Robolectric.setupActivity(TestClass.class); - TestClass activity = spy(orgActivity); - - List info = activity.onQueryPackageManager(CreateShortcut.getBaseIntent()); + final List info = mController.queryShortcuts(); assertThat(info).hasSize(1); assertThat(info.get(0)).isEqualTo(ri2); } + @Test + public void queryShortcuts_shouldSortBasedOnPriority() { + final ResolveInfo ri1 = new ResolveInfo(); + ri1.priority = 100; + ri1.activityInfo = new ActivityInfo(); + ri1.activityInfo.name = "activity1"; + ri1.activityInfo.applicationInfo = new ApplicationInfo(); + ri1.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; - private static class TestClass extends CreateShortcut { + final ResolveInfo ri2 = new ResolveInfo(); + ri1.priority = 50; + ri2.activityInfo = new ActivityInfo(); + ri2.activityInfo.name = "activity2"; + ri2.activityInfo.applicationInfo = new ApplicationInfo(); + ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + mPackageManager.addResolveInfoForIntent( + new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE), + Arrays.asList(ri1, ri2)); + + final List info = mController.queryShortcuts(); + assertThat(info).hasSize(2); + assertThat(info.get(0)).isEqualTo(ri2); + assertThat(info.get(1)).isEqualTo(ri1); } } diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java index 9a1443973b8..955e06979cc 100644 --- a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java +++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java @@ -16,7 +16,7 @@ package com.android.settings.shortcut; -import static com.android.settings.shortcut.CreateShortcut.SHORTCUT_ID_PREFIX; +import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -63,7 +63,6 @@ public class ShortcutsUpdateTaskTest { @Captor private ArgumentCaptor> mListCaptor; - @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -75,11 +74,13 @@ public class ShortcutsUpdateTaskTest { public void shortcutsUpdateTask() { mContext = spy(RuntimeEnvironment.application); doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE)); - final Intent shortcut1 = CreateShortcut.getBaseIntent().setComponent( - new ComponentName(mContext, Settings.ManageApplicationsActivity.class)); + final Intent shortcut1 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE) + .setComponent(new ComponentName( + mContext, Settings.ManageApplicationsActivity.class)); final ResolveInfo ri1 = mock(ResolveInfo.class); - final Intent shortcut2 = CreateShortcut.getBaseIntent().setComponent( - new ComponentName(mContext, Settings.SoundSettingsActivity.class)); + final Intent shortcut2 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE) + .setComponent(new ComponentName( + mContext, Settings.SoundSettingsActivity.class)); final ResolveInfo ri2 = mock(ResolveInfo.class); when(ri1.loadLabel(any(PackageManager.class))).thenReturn("label1"); when(ri2.loadLabel(any(PackageManager.class))).thenReturn("label2");