summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk3
-rw-r--r--AndroidManifest-common.xml2
-rw-r--r--res/xml/launcher_preferences.xml11
-rw-r--r--src/com/android/launcher3/SettingsActivity.java326
-rw-r--r--src/com/android/launcher3/graphics/IconShapeOverride.java6
-rw-r--r--src/com/android/launcher3/settings/IconBadgingPreference.java138
-rw-r--r--src/com/android/launcher3/settings/PreferenceHighlighter.java127
-rw-r--r--src/com/android/launcher3/settings/SettingsActivity.java250
-rw-r--r--src/com/android/launcher3/util/ListViewHighlighter.java142
-rw-r--r--src/com/android/launcher3/views/ButtonPreference.java65
10 files changed, 527 insertions, 543 deletions
diff --git a/Android.mk b/Android.mk
index b8e6c858f..bf9b8a0d7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -67,7 +67,8 @@ LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.recyclerview_recyclerview \
- androidx.dynamicanimation_dynamicanimation
+ androidx.dynamicanimation_dynamicanimation \
+ androidx.preference_preference
LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 8f4d5bece..5b00b7d02 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -156,7 +156,7 @@
The settings activity. To extend point settings_fragment_name to appropriate fragment class
-->
<activity
- android:name="com.android.launcher3.SettingsActivity"
+ android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
android:theme="@android:style/Theme.DeviceDefault.Settings"
android:autoRemoveFromRecents="true">
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 1df7c2fba..c55cc4985 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -14,9 +14,10 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<androidx.preference.PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.launcher3.views.ButtonPreference
+ <com.android.launcher3.settings.IconBadgingPreference
android:key="pref_icon_badging"
android:title="@string/icon_badging_title"
android:persistent="false"
@@ -27,7 +28,7 @@
android:name=":settings:fragment_args_key"
android:value="notification_badging" />
</intent>
- </com.android.launcher3.views.ButtonPreference>
+ </com.android.launcher3.settings.IconBadgingPreference>
<SwitchPreference
android:key="pref_add_icon_to_home"
@@ -52,10 +53,10 @@
android:defaultValue=""
android:persistent="false" />
- <PreferenceScreen
+ <androidx.preference.PreferenceScreen
android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
android:key="flag_toggler"
android:persistent="false"
android:title="Feature flags"/>
-</PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
deleted file mode 100644
index a17f61419..000000000
--- a/src/com/android/launcher3/SettingsActivity.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2015 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.launcher3;
-
-import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
-import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.Adapter;
-import android.widget.ListView;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.IconShapeOverride;
-import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.ListViewHighlighter;
-import com.android.launcher3.util.SecureSettingsObserver;
-import com.android.launcher3.views.ButtonPreference;
-
-import java.util.Objects;
-
-/**
- * Settings activity for Launcher. Currently implements the following setting: Allow rotation
- */
-public class SettingsActivity extends Activity
- implements PreferenceFragment.OnPreferenceStartFragmentCallback {
-
- private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
-
- private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
- /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
- private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
-
- private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
- private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
- private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
- private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (savedInstanceState == null) {
- Fragment f = Fragment.instantiate(this, getString(R.string.settings_fragment_name));
- // Display the fragment as the main content.
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, f)
- .commit();
- }
- }
-
- protected PreferenceFragment getNewFragment() {
- return new LauncherSettingsFragment();
- }
-
- @Override
- public boolean onPreferenceStartFragment(
- PreferenceFragment preferenceFragment, Preference pref) {
- if (getFragmentManager().isStateSaved()) {
- // Sometimes onClick can come after onPause because of being posted on the handler.
- // Skip starting new fragments in that case.
- return false;
- }
- Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
- if (f instanceof DialogFragment) {
- ((DialogFragment) f).show(getFragmentManager(), pref.getKey());
- } else {
- getFragmentManager()
- .beginTransaction()
- .replace(android.R.id.content, f)
- .addToBackStack(pref.getKey())
- .commit();
- }
- return true;
- }
-
- /**
- * This fragment shows the launcher preferences.
- */
- public static class LauncherSettingsFragment extends PreferenceFragment {
-
- private SecureSettingsObserver mIconBadgingObserver;
-
- private String mPreferenceKey;
- private boolean mPreferenceHighlighted = false;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
- }
-
- getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
- addPreferencesFromResource(R.xml.launcher_preferences);
-
- // Only show flag toggler UI if this build variant implements that.
- Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY);
- if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) {
- getPreferenceScreen().removePreference(flagToggler);
- }
-
- ContentResolver resolver = getActivity().getContentResolver();
-
- ButtonPreference iconBadgingPref =
- (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
- if (!Utilities.ATLEAST_OREO) {
- getPreferenceScreen().removePreference(
- findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
- getPreferenceScreen().removePreference(iconBadgingPref);
- } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) {
- getPreferenceScreen().removePreference(iconBadgingPref);
- } else {
- // Listen to system notification badge settings while this UI is active.
- mIconBadgingObserver = newNotificationSettingsObserver(
- getActivity(), new IconBadgingObserver(iconBadgingPref, resolver));
- mIconBadgingObserver.register();
- // Also listen if notification permission changes
- mIconBadgingObserver.getResolver().registerContentObserver(
- Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
- mIconBadgingObserver);
- mIconBadgingObserver.dispatchOnChange();
- }
-
- Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);
- if (iconShapeOverride != null) {
- if (IconShapeOverride.isSupported(getActivity())) {
- IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride);
- } else {
- getPreferenceScreen().removePreference(iconShapeOverride);
- }
- }
-
- // Setup allow rotation preference
- Preference rotationPref = findPreference(ALLOW_ROTATION_PREFERENCE_KEY);
- if (getResources().getBoolean(R.bool.allow_rotation)) {
- // Launcher supports rotation by default. No need to show this setting.
- getPreferenceScreen().removePreference(rotationPref);
- } else {
- // Initialize the UI once
- rotationPref.setDefaultValue(getAllowRotationDefaultValue());
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- Intent intent = getActivity().getIntent();
- mPreferenceKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
- if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) {
- getView().postDelayed(this::highlightPreference, DELAY_HIGHLIGHT_DURATION_MILLIS);
- }
- }
-
- private void highlightPreference() {
- Preference pref = findPreference(mPreferenceKey);
- if (pref == null || getPreferenceScreen() == null) {
- return;
- }
- PreferenceScreen screen = getPreferenceScreen();
- if (Utilities.ATLEAST_OREO) {
- screen = selectPreferenceRecursive(pref, screen);
- }
- if (screen == null) {
- return;
- }
-
- View root = screen.getDialog() != null
- ? screen.getDialog().getWindow().getDecorView() : getView();
- ListView list = root.findViewById(android.R.id.list);
- if (list == null || list.getAdapter() == null) {
- return;
- }
- Adapter adapter = list.getAdapter();
-
- // Find the position
- int position = -1;
- for (int i = adapter.getCount() - 1; i >= 0; i--) {
- if (pref == adapter.getItem(i)) {
- position = i;
- break;
- }
- }
- new ListViewHighlighter(list, position);
- mPreferenceHighlighted = true;
- }
-
- @Override
- public void onDestroy() {
- if (mIconBadgingObserver != null) {
- mIconBadgingObserver.unregister();
- mIconBadgingObserver = null;
- }
- super.onDestroy();
- }
-
- @TargetApi(Build.VERSION_CODES.O)
- private PreferenceScreen selectPreferenceRecursive(
- Preference pref, PreferenceScreen topParent) {
- if (!(pref.getParent() instanceof PreferenceScreen)) {
- return null;
- }
-
- PreferenceScreen parent = (PreferenceScreen) pref.getParent();
- if (Objects.equals(parent.getKey(), topParent.getKey())) {
- return parent;
- } else if (selectPreferenceRecursive(parent, topParent) != null) {
- ((PreferenceScreen) parent.getParent())
- .onItemClick(null, null, parent.getOrder(), 0);
- return parent;
- } else {
- return null;
- }
- }
- }
-
- /**
- * Content observer which listens for system badging setting changes,
- * and updates the launcher badging setting subtext accordingly.
- */
- private static class IconBadgingObserver implements SecureSettingsObserver.OnChangeListener {
-
- private final ButtonPreference mBadgingPref;
- private final ContentResolver mResolver;
-
- public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver) {
- mBadgingPref = badgingPref;
- mResolver = resolver;
- }
-
- @Override
- public void onSettingsChanged(boolean enabled) {
- int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
-
- boolean serviceEnabled = true;
- if (enabled) {
- // Check if the listener is enabled or not.
- String enabledListeners =
- Settings.Secure.getString(mResolver, NOTIFICATION_ENABLED_LISTENERS);
- ComponentName myListener =
- new ComponentName(mBadgingPref.getContext(), NotificationListener.class);
- serviceEnabled = enabledListeners != null &&
- (enabledListeners.contains(myListener.flattenToString()) ||
- enabledListeners.contains(myListener.flattenToShortString()));
- if (!serviceEnabled) {
- summary = R.string.title_missing_notification_access;
- }
- }
- mBadgingPref.setWidgetFrameVisible(!serviceEnabled);
- mBadgingPref.setFragment(
- serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
- mBadgingPref.setSummary(summary);
-
- }
- }
-
- public static class NotificationAccessConfirmation
- extends DialogFragment implements DialogInterface.OnClickListener {
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- String msg = context.getString(R.string.msg_missing_notification_access,
- context.getString(R.string.derived_app_name));
- return new AlertDialog.Builder(context)
- .setTitle(R.string.title_missing_notification_access)
- .setMessage(msg)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(R.string.title_change_settings, this)
- .create();
- }
-
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
- Bundle showFragmentArgs = new Bundle();
- showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
-
- Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
- .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
- getActivity().startActivity(intent);
- }
- }
-}
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java
index cadc6e35e..b636c6d47 100644
--- a/src/com/android/launcher3/graphics/IconShapeOverride.java
+++ b/src/com/android/launcher3/graphics/IconShapeOverride.java
@@ -26,9 +26,6 @@ import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.os.SystemClock;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -42,6 +39,9 @@ import com.android.launcher3.util.LooperExecutor;
import java.lang.reflect.Field;
import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
/**
* Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}.
diff --git a/src/com/android/launcher3/settings/IconBadgingPreference.java b/src/com/android/launcher3/settings/IconBadgingPreference.java
new file mode 100644
index 000000000..7c97b38d2
--- /dev/null
+++ b/src/com/android/launcher3/settings/IconBadgingPreference.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 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.launcher3.settings;
+
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGS;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.util.SecureSettingsObserver;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A {@link Preference} for indicating icon badging status.
+ * Also has utility methods for updating UI based on badging status changes.
+ */
+public class IconBadgingPreference extends Preference
+ implements SecureSettingsObserver.OnChangeListener {
+
+ private boolean mWidgetFrameVisible = false;
+
+ /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+ private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+
+ public IconBadgingPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public IconBadgingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public IconBadgingPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public IconBadgingPreference(Context context) {
+ super(context);
+ }
+
+ private void setWidgetFrameVisible(boolean isVisible) {
+ if (mWidgetFrameVisible != isVisible) {
+ mWidgetFrameVisible = isVisible;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ View widgetFrame = holder.findViewById(android.R.id.widget_frame);
+ if (widgetFrame != null) {
+ widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void onSettingsChanged(boolean enabled) {
+ int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
+
+ boolean serviceEnabled = true;
+ if (enabled) {
+ // Check if the listener is enabled or not.
+ String enabledListeners = Settings.Secure.getString(
+ getContext().getContentResolver(), NOTIFICATION_ENABLED_LISTENERS);
+ ComponentName myListener =
+ new ComponentName(getContext(), NotificationListener.class);
+ serviceEnabled = enabledListeners != null &&
+ (enabledListeners.contains(myListener.flattenToString()) ||
+ enabledListeners.contains(myListener.flattenToShortString()));
+ if (!serviceEnabled) {
+ summary = R.string.title_missing_notification_access;
+ }
+ }
+ setWidgetFrameVisible(!serviceEnabled);
+ setFragment(serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
+ setSummary(summary);
+ }
+
+ public static class NotificationAccessConfirmation
+ extends DialogFragment implements DialogInterface.OnClickListener {
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ String msg = context.getString(R.string.msg_missing_notification_access,
+ context.getString(R.string.derived_app_name));
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.title_missing_notification_access)
+ .setMessage(msg)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.title_change_settings, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
+ Bundle showFragmentArgs = new Bundle();
+ showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
+
+ Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
+ .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
+ getActivity().startActivity(intent);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/settings/PreferenceHighlighter.java b/src/com/android/launcher3/settings/PreferenceHighlighter.java
new file mode 100644
index 000000000..4ed4cf113
--- /dev/null
+++ b/src/com/android/launcher3/settings/PreferenceHighlighter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.launcher3.settings;
+
+import static androidx.core.graphics.ColorUtils.setAlphaComponent;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.util.Themes;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.State;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Utility class for highlighting a preference
+ */
+public class PreferenceHighlighter extends ItemDecoration implements Runnable {
+
+ private static final Property<PreferenceHighlighter, Integer> HIGHLIGHT_COLOR =
+ new Property<PreferenceHighlighter, Integer>(Integer.TYPE, "highlightColor") {
+
+ @Override
+ public Integer get(PreferenceHighlighter highlighter) {
+ return highlighter.mHighlightColor;
+ }
+
+ @Override
+ public void set(PreferenceHighlighter highlighter, Integer value) {
+ highlighter.mHighlightColor = value;
+ highlighter.mRv.invalidateItemDecorations();
+ }
+ };
+
+ private static final long HIGHLIGHT_DURATION = 15000L;
+ private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
+ private static final long HIGHLIGHT_FADE_IN_DURATION = 200L;
+ private static final int END_COLOR = setAlphaComponent(Color.WHITE, 0);
+
+ private final Paint mPaint = new Paint();
+ private final RecyclerView mRv;
+ private final int mIndex;
+
+ private boolean mHighLightStarted = false;
+ private int mHighlightColor = END_COLOR;
+
+
+ public PreferenceHighlighter(RecyclerView rv, int index) {
+ mRv = rv;
+ mIndex = index;
+ }
+
+ @Override
+ public void run() {
+ mRv.addItemDecoration(this);
+ mRv.smoothScrollToPosition(mIndex);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, State state) {
+ ViewHolder holder = parent.findViewHolderForAdapterPosition(mIndex);
+ if (holder == null) {
+ return;
+ }
+ if (!mHighLightStarted && state.getRemainingScrollVertical() != 0) {
+ // Wait until scrolling stopped
+ return;
+ }
+
+ if (!mHighLightStarted) {
+ // Start highlight
+ int colorTo = setAlphaComponent(Themes.getColorAccent(mRv.getContext()), 66);
+ ObjectAnimator anim = ObjectAnimator.ofArgb(this, HIGHLIGHT_COLOR, END_COLOR, colorTo);
+ anim.setDuration(HIGHLIGHT_FADE_IN_DURATION);
+ anim.setRepeatMode(ValueAnimator.REVERSE);
+ anim.setRepeatCount(4);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ removeHighlight();
+ }
+ });
+ anim.start();
+ mHighLightStarted = true;
+ }
+
+ View view = holder.itemView;
+ mPaint.setColor(mHighlightColor);
+ c.drawRect(0, view.getY(), parent.getWidth(), view.getY() + view.getHeight(), mPaint);
+ }
+
+ private void removeHighlight() {
+ ObjectAnimator anim = ObjectAnimator.ofArgb(
+ this, HIGHLIGHT_COLOR, mHighlightColor, END_COLOR);
+ anim.setDuration(HIGHLIGHT_FADE_OUT_DURATION);
+ anim.setStartDelay(HIGHLIGHT_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRv.removeItemDecoration(PreferenceHighlighter.this);
+ }
+ });
+ anim.start();
+ }
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
new file mode 100644
index 000000000..66420d079
--- /dev/null
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 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.launcher3.settings;
+
+import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.util.SecureSettingsObserver;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceGroup.PreferencePositionCallback;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Settings activity for Launcher. Currently implements the following setting: Allow rotation
+ */
+public class SettingsActivity extends Activity
+ implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+
+ private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
+
+ private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
+ /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+ private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+
+ public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+ public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
+ private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
+ public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ Bundle args = new Bundle();
+ String prefKey = getIntent().getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
+ if (!TextUtils.isEmpty(prefKey)) {
+ args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
+ }
+
+ Fragment f = Fragment.instantiate(
+ this, getString(R.string.settings_fragment_name), args);
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, f)
+ .commit();
+ }
+ }
+
+ private boolean startFragment(String fragment, Bundle args, String key) {
+ if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) {
+ // Sometimes onClick can come after onPause because of being posted on the handler.
+ // Skip starting new fragments in that case.
+ return false;
+ }
+ Fragment f = Fragment.instantiate(this, fragment, args);
+ if (f instanceof DialogFragment) {
+ ((DialogFragment) f).show(getFragmentManager(), key);
+ } else {
+ getFragmentManager()
+ .beginTransaction()
+ .replace(android.R.id.content, f)
+ .addToBackStack(key)
+ .commit();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(
+ PreferenceFragment preferenceFragment, Preference pref) {
+ return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+ Bundle args = new Bundle();
+ args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey());
+ }
+
+ /**
+ * This fragment shows the launcher preferences.
+ */
+ public static class LauncherSettingsFragment extends PreferenceFragment {
+
+ private SecureSettingsObserver mIconBadgingObserver;
+
+ private String mHighLightKey;
+ private boolean mPreferenceHighlighted = false;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ final Bundle args = getArguments();
+ mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY);
+ if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) {
+ rootKey = getParentKeyForPref(mHighLightKey);
+ }
+
+ if (savedInstanceState != null) {
+ mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
+ }
+
+ getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
+ setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
+
+ PreferenceScreen screen = getPreferenceScreen();
+ for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
+ Preference preference = screen.getPreference(i);
+ if (!initPreference(preference)) {
+ screen.removePreference(preference);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
+ }
+
+ protected String getParentKeyForPref(String key) {
+ return null;
+ }
+
+ /**
+ * Initializes a preference. This is called for every preference. Returning false here
+ * will remove that preference from the list.
+ */
+ protected boolean initPreference(Preference preference) {
+ switch (preference.getKey()) {
+ case ICON_BADGING_PREFERENCE_KEY:
+ if (!Utilities.ATLEAST_OREO ||
+ !getResources().getBoolean(R.bool.notification_badging_enabled)) {
+ return false;
+ }
+
+ // Listen to system notification badge settings while this UI is active.
+ mIconBadgingObserver = newNotificationSettingsObserver(
+ getActivity(), (IconBadgingPreference) preference);
+ mIconBadgingObserver.register();
+ // Also listen if notification permission changes
+ mIconBadgingObserver.getResolver().registerContentObserver(
+ Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
+ mIconBadgingObserver);
+ mIconBadgingObserver.dispatchOnChange();
+ return true;
+
+ case ADD_ICON_PREFERENCE_KEY:
+ return Utilities.ATLEAST_OREO;
+
+ case IconShapeOverride.KEY_PREFERENCE:
+ if (!IconShapeOverride.isSupported(getActivity())) {
+ return false;
+ }
+ IconShapeOverride.handlePreferenceUi((ListPreference) preference);
+ return true;
+
+ case ALLOW_ROTATION_PREFERENCE_KEY:
+ if (getResources().getBoolean(R.bool.allow_rotation)) {
+ // Launcher supports rotation by default. No need to show this setting.
+ return false;
+ }
+ // Initialize the UI once
+ preference.setDefaultValue(getAllowRotationDefaultValue());
+ return true;
+
+ case FLAGS_PREFERENCE_KEY:
+ // Only show flag toggler UI if this build variant implements that.
+ return FeatureFlags.showFlagTogglerUi();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (isAdded() && !mPreferenceHighlighted) {
+ PreferenceHighlighter highlighter = createHighlighter();
+ if (highlighter != null) {
+ getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
+ mPreferenceHighlighted = true;
+ }
+ }
+ }
+
+ private PreferenceHighlighter createHighlighter() {
+ if (TextUtils.isEmpty(mHighLightKey)) {
+ return null;
+ }
+
+ PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ return null;
+ }
+
+ RecyclerView list = getListView();
+ PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter();
+ int position = callback.getPreferenceAdapterPosition(mHighLightKey);
+ return position >= 0 ? new PreferenceHighlighter(list, position) : null;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mIconBadgingObserver != null) {
+ mIconBadgingObserver.unregister();
+ mIconBadgingObserver = null;
+ }
+ super.onDestroy();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ListViewHighlighter.java b/src/com/android/launcher3/util/ListViewHighlighter.java
deleted file mode 100644
index c9fe228d5..000000000
--- a/src/com/android/launcher3/util/ListViewHighlighter.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.launcher3.util;
-
-import android.animation.ArgbEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AbsListView.RecyclerListener;
-import android.widget.ListView;
-
-import com.android.launcher3.R;
-
-import androidx.core.graphics.ColorUtils;
-
-/**
- * Utility class to scroll and highlight a list view item
- */
-public class ListViewHighlighter implements OnScrollListener, RecyclerListener,
- OnLayoutChangeListener {
-
- private final ListView mListView;
- private int mPosHighlight;
-
- private boolean mColorAnimated = false;
-
- public ListViewHighlighter(ListView listView, int posHighlight) {
- mListView = listView;
- mPosHighlight = posHighlight;
- mListView.setOnScrollListener(this);
- mListView.setRecyclerListener(this);
- mListView.addOnLayoutChangeListener(this);
-
- mListView.post(this::tryHighlight);
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mListView.post(this::tryHighlight);
- }
-
- private void tryHighlight() {
- if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
- return;
- }
- if (!highlightIfVisible(mListView.getFirstVisiblePosition(),
- mListView.getLastVisiblePosition())) {
- mListView.smoothScrollToPosition(mPosHighlight);
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView absListView, int i) { }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount - 1);
- }
-
- private boolean highlightIfVisible(int start, int end) {
- if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
- return false;
- }
- if (start > mPosHighlight || mPosHighlight > end) {
- return false;
- }
- highlightView(mListView.getChildAt(mPosHighlight - start));
-
- // finish highlight
- mListView.setOnScrollListener(null);
- mListView.removeOnLayoutChangeListener(this);
-
- mPosHighlight = -1;
- return true;
- }
-
- @Override
- public void onMovedToScrapHeap(View view) {
- unhighlightView(view);
- }
-
- private void highlightView(View view) {
- if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
- // already highlighted
- } else {
- view.setTag(R.id.view_highlighted, true);
- view.setTag(R.id.view_unhighlight_background, view.getBackground());
- view.setBackground(getHighlightBackground());
- view.postDelayed(() -> {
- unhighlightView(view);
- }, 15000L);
- }
- }
-
- private void unhighlightView(View view) {
- if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
- Object background = view.getTag(R.id.view_unhighlight_background);
- if (background instanceof Drawable) {
- view.setBackground((Drawable) background);
- }
- view.setTag(R.id.view_unhighlight_background, null);
- view.setTag(R.id.view_highlighted, false);
- }
- }
-
- private ColorDrawable getHighlightBackground() {
- int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(mListView.getContext()), 26);
- if (mColorAnimated) {
- return new ColorDrawable(color);
- }
- mColorAnimated = true;
- ColorDrawable bg = new ColorDrawable(Color.WHITE);
- ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color);
- anim.setEvaluator(new ArgbEvaluator());
- anim.setDuration(200L);
- anim.setRepeatMode(ValueAnimator.REVERSE);
- anim.setRepeatCount(4);
- anim.start();
- return bg;
- }
-}
diff --git a/src/com/android/launcher3/views/ButtonPreference.java b/src/com/android/launcher3/views/ButtonPreference.java
deleted file mode 100644
index fdcf2ca5b..000000000
--- a/src/com/android/launcher3/views/ButtonPreference.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.views;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Extension of {@link Preference} which makes the widget layout clickable.
- *
- * @see #setWidgetLayoutResource(int)
- */
-public class ButtonPreference extends Preference {
-
- private boolean mWidgetFrameVisible = false;
-
- public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public ButtonPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ButtonPreference(Context context) {
- super(context);
- }
-
- public void setWidgetFrameVisible(boolean isVisible) {
- if (mWidgetFrameVisible != isVisible) {
- mWidgetFrameVisible = isVisible;
- notifyChanged();
- }
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
-
- ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
- if (widgetFrame != null) {
- widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
- }
- }
-}