Snap for 5085055 from 17170c698c to qt-release

Change-Id: I0602e34b126f1d0862e44582a303776845d639bc
This commit is contained in:
android-build-team Robot
2018-10-23 03:12:24 +00:00
46 changed files with 1225 additions and 693 deletions
+20
View File
@@ -28,3 +28,23 @@ java_library_static {
],
platform_apis: true,
}
android_library {
name: "icon-loader",
sdk_version: "28",
static_libs: [
"androidx.core_core",
],
resource_dirs: [
"res",
],
srcs: [
"src/com/android/launcher3/icons/BaseIconFactory.java",
"src/com/android/launcher3/icons/BitmapInfo.java",
"src/com/android/launcher3/icons/IconNormalizer.java",
"src/com/android/launcher3/icons/FixedScaleDrawable.java",
"src/com/android/launcher3/icons/ShadowGenerator.java",
"src/com/android/launcher3/icons/ColorExtractor.java",
],
}
+2 -1
View File
@@ -66,7 +66,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 := LauncherPluginLib
+1 -1
View File
@@ -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">
+4
View File
@@ -0,0 +1,4 @@
adamcohen@google.com
hyunyoungs@google.com
sunnygoyal@google.com
winsonc@google.com
+1
View File
@@ -112,6 +112,7 @@ enum ControlType {
CANCEL_TARGET = 14;
TASK_PREVIEW = 15;
SPLIT_SCREEN_TARGET = 16;
REMOTE_ACTION_SHORTCUT = 17;
}
enum TipType {
@@ -20,21 +20,34 @@ import android.content.Context;
import android.graphics.Matrix;
import android.view.View;
import androidx.annotation.AnyThread;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import androidx.annotation.AnyThread;
import java.util.ArrayList;
import java.util.List;
/**
* Factory class to create and add an overlays on the TaskView
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
private static TaskOverlayFactory sInstance;
/** Note that these will be shown in order from top to bottom, if available for the task. */
private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
new TaskSystemShortcut.AppInfo(),
new TaskSystemShortcut.SplitScreen(),
new TaskSystemShortcut.Pin(),
new TaskSystemShortcut.Install(),
};
public static TaskOverlayFactory get(Context context) {
Preconditions.assertUIThread();
if (sInstance == null) {
@@ -55,9 +68,23 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
public static class TaskOverlay {
public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) { }
public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) {
}
public void reset() { }
public void reset() {
}
public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
View.OnClickListener onClickListener =
menuOption.getOnClickListener(activity, taskView);
if (onClickListener != null) {
shortcuts.add(menuOption);
}
}
return shortcuts;
}
}
}
@@ -540,7 +540,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
public void updateDisplacement(float displacement) {
// We are moving in the negative x/y direction
displacement = -displacement;
if (displacement > mTransitionDragLength) {
if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
mCurrentShift.updateValue(1);
if (!mBgLongSwipeMode) {
@@ -815,8 +815,12 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
long startMillis = SystemClock.uptimeMillis();
executeOnUiThread(() -> {
// Animate the launcher components at the same time as the window, always on UI thread.
if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible
&& start != end && duration > 0) {
if (mLauncherTransitionController == null) {
return;
}
if (start == end || duration <= 0) {
mLauncherTransitionController.getAnimationPlayer().end();
} else {
// Adjust start progress and duration in case we are on a different thread.
long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
@@ -43,6 +43,8 @@ import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.IconView.OnScaleUpdateListener;
import java.util.List;
/**
* Contains options for a recent task when long-pressing its icon.
*/
@@ -50,14 +52,6 @@ public class TaskMenuView extends AbstractFloatingView {
private static final Rect sTempRect = new Rect();
/** Note that these will be shown in order from top to bottom, if available for the task. */
public static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] {
new TaskSystemShortcut.AppInfo(),
new TaskSystemShortcut.SplitScreen(),
new TaskSystemShortcut.Pin(),
new TaskSystemShortcut.Install(),
};
private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() {
@Override
public void onScaleUpdate(float scale) {
@@ -197,11 +191,13 @@ public class TaskMenuView extends AbstractFloatingView {
params.topMargin = (int) -mThumbnailTopMargin;
mTaskIcon.setLayoutParams(params);
for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
if (onClickListener != null) {
addMenuOption(menuOption, onClickListener);
}
final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
final List<TaskSystemShortcut> shortcuts =
taskView.getTaskOverlay().getEnabledShortcuts(taskView);
final int count = shortcuts.size();
for (int i = 0; i < count; ++i) {
final TaskSystemShortcut menuOption = shortcuts.get(i);
addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
}
}
@@ -173,6 +173,10 @@ public class TaskThumbnailView extends View {
return 0;
}
public TaskOverlay getTaskOverlay() {
return mOverlay;
}
@Override
protected void onDraw(Canvas canvas) {
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
@@ -42,12 +42,12 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.RecentsView.PageCallbacks;
@@ -56,8 +56,9 @@ import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import java.util.List;
import java.util.function.Consumer;
/**
@@ -190,6 +191,10 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
return mIconView;
}
public TaskOverlayFactory.TaskOverlay getTaskOverlay() {
return mSnapshotView.getTaskOverlay();
}
public void launchTask(boolean animate) {
launchTask(animate, (result) -> {
if (!result) {
@@ -384,7 +389,11 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
final Context context = getContext();
final BaseDraggingActivity activity = fromContext(context);
for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
final List<TaskSystemShortcut> shortcuts =
mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
final int count = shortcuts.size();
for (int i = 0; i < count; ++i) {
final TaskSystemShortcut menuOption = shortcuts.get(i);
OnClickListener onClickListener = menuOption.getOnClickListener(activity, this);
if (onClickListener != null) {
info.addAction(menuOption.createAccessibilityAction(context));
@@ -407,7 +416,11 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
return true;
}
for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
final List<TaskSystemShortcut> shortcuts =
mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
final int count = shortcuts.size();
for (int i = 0; i < count; ++i) {
final TaskSystemShortcut menuOption = shortcuts.get(i);
if (menuOption.hasHandlerForAction(action)) {
OnClickListener onClickListener = menuOption.getOnClickListener(
fromContext(getContext()), this);
+1
View File
@@ -123,6 +123,7 @@
<item type="id" name="action_deep_shortcuts" />
<item type="id" name="action_shortcuts_and_notifications"/>
<item type="id" name="action_dismiss_notification" />
<item type="id" name="action_remote_action_shortcut" />
<!-- QSB IDs. DO not change -->
<item type="id" name="search_container_workspace" />
+3
View File
@@ -347,4 +347,7 @@
<string name="work_mode_off_label">Notifications and apps are off</string>
<string name="bottom_work_tab_user_education_close_button">Close</string>
<string name="bottom_work_tab_user_education_closed">Closed</string>
<!-- Failed action error message: e.g. Failed: Pause -->
<string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
</resources>
+6 -5
View File
@@ -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>
@@ -480,7 +480,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
final LauncherAppState app = LauncherAppState.getInstance(mContext);
// Set default values until proper values is loaded.
appInfo.title = "";
app.getIconCache().getDefaultIcon(user).applyTo(appInfo);
appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
final ShortcutInfo si = appInfo.makeShortcut();
if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
@@ -495,7 +495,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
} else if (shortcutInfo != null) {
ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
LauncherIcons li = LauncherIcons.obtain(mContext);
li.createShortcutIcon(shortcutInfo).applyTo(si);
si.applyFrom(li.createShortcutIcon(shortcutInfo));
li.recycle();
return Pair.create((ItemInfo) si, (Object) shortcutInfo);
} else if (providerInfo != null) {
@@ -656,7 +656,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
if (iconInfo == null) {
iconInfo = app.getIconCache().getDefaultIcon(info.user);
}
iconInfo.applyTo(info);
info.applyFrom(iconInfo);
info.title = Utilities.trim(name);
info.contentDescription = UserManagerCompat.getInstance(app.getContext())
@@ -20,6 +20,8 @@ import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
import android.graphics.Bitmap;
import com.android.launcher3.icons.BitmapInfo;
/**
* Represents an ItemInfo which also holds an icon.
*/
@@ -118,4 +120,10 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
public boolean usingLowResIcon() {
return iconBitmap == LOW_RES_ICON;
}
public void applyFrom(BitmapInfo info) {
iconBitmap = info.icon;
iconColor = info.color;
}
}
+1 -1
View File
@@ -657,7 +657,7 @@ public class LauncherModel extends BroadcastReceiver
updateAndBindShortcutInfo(() -> {
si.updateFromDeepShortcutInfo(info, mApp.getContext());
LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
li.createShortcutIcon(info).applyTo(si);
si.applyFrom(li.createShortcutIcon(info));
li.recycle();
return si;
});
@@ -19,6 +19,7 @@ package com.android.launcher3;
import android.content.Context;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderShape;
import com.android.launcher3.graphics.IconShapeOverride;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -39,5 +40,6 @@ public class MainProcessInitializer implements ResourceBasedOverride {
FeatureFlags.initialize(context);
IconShapeOverride.apply(context);
SessionCommitReceiver.applyDefaultUserPrefs(context);
FolderShape.init();
}
}
@@ -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);
}
}
}
+3 -3
View File
@@ -127,8 +127,8 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
private static final int DEFAULT_PAGE = 0;
private static final boolean MAP_NO_RECURSE = false;
private static final boolean MAP_RECURSE = true;
public static final boolean MAP_NO_RECURSE = false;
public static final boolean MAP_RECURSE = true;
// The screen id used for the empty screen always present to the right.
public static final int EXTRA_EMPTY_SCREEN_ID = -201;
@@ -3121,7 +3121,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
* @param recurse true: iterate over folder children. false: op get the folders themselves.
* @param op the operator to map over the shortcuts
*/
void mapOverItems(boolean recurse, ItemOperator op) {
public void mapOverItems(boolean recurse, ItemOperator op) {
ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
final int containerCount = containers.size();
for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
@@ -139,7 +139,7 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
ShortcutInfo info = new ShortcutInfo(compat, context);
// Apply the unbadged icon and fetch the actual icon asynchronously.
LauncherIcons li = LauncherIcons.obtain(context);
li.createShortcutIcon(compat, false /* badged */).applyTo(info);
info.applyFrom(li.createShortcutIcon(compat, false /* badged */));
li.recycle();
LauncherAppState.getInstance(context).getModel()
.updateAndBindShortcutInfo(info, compat);
@@ -204,7 +204,7 @@ public class DragView extends View {
public void run() {
LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
Object[] outObj = new Object[1];
final Drawable dr = getFullDrawable(info, appState, outObj);
Drawable dr = getFullDrawable(info, appState, outObj);
if (dr instanceof AdaptiveIconDrawable) {
int w = mBitmap.getWidth();
@@ -220,10 +220,20 @@ public class DragView extends View {
mBadge = getBadge(info, appState, outObj[0]);
mBadge.setBounds(badgeBounds);
LauncherIcons li = LauncherIcons.obtain(mLauncher);
Utilities.scaleRectAboutCenter(bounds,
li.getNormalizer().getScale(dr, null, null, null));
li.recycle();
// Do not draw the background in case of folder as its translucent
mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
Drawable nDr; // drawable to be normalized
if (mDrawBitmap) {
nDr = dr;
} else {
// Since we just want the scale, avoid heavy drawing operations
nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
}
Utilities.scaleRectAboutCenter(bounds,
li.getNormalizer().getScale(nDr, null, null, null));
}
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
// Shrink very tiny bit so that the clip path is smaller than the original bitmap
@@ -259,9 +269,6 @@ public class DragView extends View {
// Assign the variable on the UI thread to avoid race conditions.
mScaledMaskPath = mask;
// Do not draw the background in case of folder as its translucent
mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
if (info.isDisabled()) {
FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null);
d.setIsDisabled(true);
@@ -26,6 +26,8 @@ import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
import android.text.InputType;
import android.text.Selection;
@@ -151,6 +153,8 @@ public class Folder extends AbstractFloatingView implements DragSource,
// Cell ranks used for drag and drop
@Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank;
private Path mClipPath;
@ViewDebug.ExportedProperty(category = "launcher",
mapping = {
@ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"),
@@ -1476,4 +1480,25 @@ public class Folder extends AbstractFloatingView implements DragSource,
sHintText = res.getString(R.string.folder_hint_text);
}
}
/**
* Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
* rounded rect.
*/
public void setClipPath(Path clipPath) {
mClipPath = clipPath;
invalidate();
}
@Override
public void draw(Canvas canvas) {
if (mClipPath != null) {
int count = canvas.save();
canvas.clipPath(mClipPath);
super.draw(canvas);
canvas.restoreToCount(count);
} else {
super.draw(canvas);
}
}
}
@@ -19,6 +19,7 @@ package com.android.launcher3.folder;
import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.FolderShape.getShape;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -40,7 +41,6 @@ import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.Themes;
@@ -166,7 +166,6 @@ public class FolderAnimationManager {
Math.round((totalOffsetX + initialSize) / initialScale),
Math.round((paddingOffsetY + initialSize) / initialScale));
Rect endRect = new Rect(0, 0, lp.width, lp.height);
float initialRadius = initialSize / initialScale / 2f;
float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
// Create the animators.
@@ -189,14 +188,8 @@ public class FolderAnimationManager {
play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
RoundedRectRevealOutlineProvider outlineProvider = new RoundedRectRevealOutlineProvider(
initialRadius, finalRadius, startRect, endRect) {
@Override
public boolean shouldRemoveElevationDuringAnimation() {
return true;
}
};
play(a, outlineProvider.createRevealAnimator(mFolder, !mIsOpening));
play(a, getShape().createRevealAnimator(
mFolder, startRect, endRect, finalRadius, !mIsOpening));
// Animate the elevation midway so that the shadow is not noticeable in the background.
int midDuration = mDuration / 2;
@@ -75,6 +75,7 @@ import androidx.annotation.NonNull;
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener {
@Thunk Launcher mLauncher;
@Thunk Folder mFolder;
private FolderInfo mInfo;
@@ -477,20 +478,9 @@ public class FolderIcon extends FrameLayout implements FolderListener {
if (mFolder == null) return;
if (mFolder.getItemCount() == 0 && !mAnimating) return;
final int saveCount;
if (canvas.isHardwareAccelerated()) {
saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
} else {
saveCount = canvas.save();
canvas.clipPath(mBackground.getClipPath());
}
final int saveCount = canvas.save();
canvas.clipPath(mBackground.getClipPath());
mPreviewItemManager.draw(canvas);
if (canvas.isHardwareAccelerated()) {
mBackground.clipCanvasHardware(canvas);
}
canvas.restoreToCount(saveCount);
if (!mBackground.drawingDelegated()) {
@@ -0,0 +1,422 @@
/*
* 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.folder;
import static com.android.launcher3.Workspace.MAP_NO_RECURSE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.FloatArrayEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.RegionIterator;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.view.ViewOutlineProvider;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
/**
* Abstract representation of the shape of a folder icon
*/
public abstract class FolderShape {
private static FolderShape sInstance = new Circle();
public static FolderShape getShape() {
return sInstance;
}
private static FolderShape[] getAllShapes() {
return new FolderShape[] {
new Circle(),
new RoundedSquare(8f / 50), // Ratios based on path defined in config_icon_mask
new RoundedSquare(30f / 50),
new Square(),
new TearDrop(),
new Squircle()};
}
public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
Paint paint);
public abstract void addShape(Path path, float offsetX, float offsetY, float radius);
public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
float endRadius, boolean isReversed);
/**
* Abstract shape where the reveal animation is a derivative of a round rect animation
*/
private static abstract class SimpleRectShape extends FolderShape {
@Override
public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
float endRadius, boolean isReversed) {
return new RoundedRectRevealOutlineProvider(
getStartRadius(startRect), endRadius, startRect, endRect) {
@Override
public boolean shouldRemoveElevationDuringAnimation() {
return true;
}
}.createRevealAnimator(target, isReversed);
}
protected abstract float getStartRadius(Rect startRect);
}
/**
* Abstract shape which draws using {@link Path}
*/
private static abstract class PathShape extends FolderShape {
private final Path mTmpPath = new Path();
@Override
public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
Paint paint) {
mTmpPath.reset();
addShape(mTmpPath, offsetX, offsetY, radius);
canvas.drawPath(mTmpPath, paint);
}
protected abstract AnimatorUpdateListener newUpdateListener(
Rect startRect, Rect endRect, float endRadius, Path outPath);
@Override
public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
float endRadius, boolean isReversed) {
Path path = new Path();
AnimatorUpdateListener listener =
newUpdateListener(startRect, endRect, endRadius, path);
ValueAnimator va =
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
va.addListener(new AnimatorListenerAdapter() {
private ViewOutlineProvider mOldOutlineProvider;
public void onAnimationStart(Animator animation) {
mOldOutlineProvider = target.getOutlineProvider();
target.setOutlineProvider(null);
target.setTranslationZ(-target.getElevation());
}
public void onAnimationEnd(Animator animation) {
target.setTranslationZ(0);
target.setClipPath(null);
target.setOutlineProvider(mOldOutlineProvider);
}
});
va.addUpdateListener((anim) -> {
path.reset();
listener.onAnimationUpdate(anim);
target.setClipPath(path);
});
return va;
}
}
public static final class Circle extends SimpleRectShape {
@Override
public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
canvas.drawCircle(radius + offsetX, radius + offsetY, radius, p);
}
@Override
public void addShape(Path path, float offsetX, float offsetY, float radius) {
path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
}
@Override
protected float getStartRadius(Rect startRect) {
return startRect.width() / 2f;
}
}
public static class Square extends SimpleRectShape {
@Override
public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
float cx = radius + offsetX;
float cy = radius + offsetY;
canvas.drawRect(cx - radius, cy - radius, cx + radius, cy + radius, p);
}
@Override
public void addShape(Path path, float offsetX, float offsetY, float radius) {
float cx = radius + offsetX;
float cy = radius + offsetY;
path.addRect(cx - radius, cy - radius, cx + radius, cy + radius, Path.Direction.CW);
}
@Override
protected float getStartRadius(Rect startRect) {
return 0;
}
}
public static class RoundedSquare extends SimpleRectShape {
/**
* Ratio of corner radius to half size. Based on the
*/
private final float mRadiusFactor;
public RoundedSquare(float radiusFactor) {
mRadiusFactor = radiusFactor;
}
@Override
public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
float cx = radius + offsetX;
float cy = radius + offsetY;
float cr = radius * mRadiusFactor;
canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
}
@Override
public void addShape(Path path, float offsetX, float offsetY, float radius) {
float cx = radius + offsetX;
float cy = radius + offsetY;
float cr = radius * mRadiusFactor;
path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
Path.Direction.CW);
}
@Override
protected float getStartRadius(Rect startRect) {
return (startRect.width() / 2f) * mRadiusFactor;
}
}
public static class TearDrop extends PathShape {
/**
* Radio of short radius to large radius, based on the shape options defined in the config.
*/
private static final float RADIUS_RATIO = 15f / 50;
private final float[] mTempRadii = new float[8];
@Override
public void addShape(Path p, float offsetX, float offsetY, float r1) {
float r2 = r1 * RADIUS_RATIO;
float cx = r1 + offsetX;
float cy = r1 + offsetY;
p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2),
Path.Direction.CW);
}
private float[] getRadiiArray(float r1, float r2) {
mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
mTempRadii[6] = mTempRadii[7] = r1;
mTempRadii[4] = mTempRadii[5] = r2;
return mTempRadii;
}
@Override
protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
float endRadius, Path outPath) {
float r1 = startRect.width() / 2f;
float r2 = r1 * RADIUS_RATIO;
float[] startValues = new float[] {
startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
float[] endValues = new float[] {
endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
return (anim) -> {
float progress = (Float) anim.getAnimatedValue();
float[] values = evaluator.evaluate(progress, startValues, endValues);
outPath.addRoundRect(
values[0], values[1], values[2], values[3],
getRadiiArray(values[4], values[5]), Path.Direction.CW);
};
}
}
public static class Squircle extends PathShape {
/**
* Radio of radius to circle radius, based on the shape options defined in the config.
*/
private static final float RADIUS_RATIO = 10f / 50;
@Override
public void addShape(Path p, float offsetX, float offsetY, float r) {
float cx = r + offsetX;
float cy = r + offsetY;
float control = r - r * RADIUS_RATIO;
p.moveTo(cx, cy - r);
addLeftCurve(cx, cy, r, control, p);
addRightCurve(cx, cy, r, control, p);
addLeftCurve(cx, cy, -r, -control, p);
addRightCurve(cx, cy, -r, -control, p);
p.close();
}
private void addLeftCurve(float cx, float cy, float r, float control, Path path) {
path.cubicTo(
cx - control, cy - r,
cx - r, cy - control,
cx - r, cy);
}
private void addRightCurve(float cx, float cy, float r, float control, Path path) {
path.cubicTo(
cx - r, cy + control,
cx - control, cy + r,
cx, cy + r);
}
@Override
protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
float endR, Path outPath) {
float startCX = startRect.exactCenterX();
float startCY = startRect.exactCenterY();
float startR = startRect.width() / 2f;
float startControl = startR - startR * RADIUS_RATIO;
float startHShift = 0;
float startVShift = 0;
float endCX = endRect.exactCenterX();
float endCY = endRect.exactCenterY();
// Approximate corner circle using bezier curves
// http://spencermortensen.com/articles/bezier-circle/
float endControl = endR * 0.551915024494f;
float endHShift = endRect.width() / 2f - endR;
float endVShift = endRect.height() / 2f - endR;
return (anim) -> {
float progress = (Float) anim.getAnimatedValue();
float cx = (1 - progress) * startCX + progress * endCX;
float cy = (1 - progress) * startCY + progress * endCY;
float r = (1 - progress) * startR + progress * endR;
float control = (1 - progress) * startControl + progress * endControl;
float hShift = (1 - progress) * startHShift + progress * endHShift;
float vShift = (1 - progress) * startVShift + progress * endVShift;
outPath.moveTo(cx, cy - vShift - r);
outPath.rLineTo(-hShift, 0);
addLeftCurve(cx - hShift, cy - vShift, r, control, outPath);
outPath.rLineTo(0, vShift + vShift);
addRightCurve(cx - hShift, cy + vShift, r, control, outPath);
outPath.rLineTo(hShift + hShift, 0);
addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath);
outPath.rLineTo(0, -vShift - vShift);
addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath);
outPath.close();
};
}
}
/**
* Initializes the shape which is closest to closest to the {@link AdaptiveIconDrawable}
*/
public static void init() {
if (!Utilities.ATLEAST_OREO) {
return;
}
new MainThreadExecutor().execute(FolderShape::pickShapeInBackground);
}
@TargetApi(Build.VERSION_CODES.O)
protected static void pickShapeInBackground() {
// Pick any large size
int size = 200;
Region full = new Region(0, 0, size, size);
Region iconR = new Region();
AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
drawable.setBounds(0, 0, size, size);
iconR.setPath(drawable.getIconMask(), full);
Path shapePath = new Path();
Region shapeR = new Region();
Rect tempRect = new Rect();
// Find the shape with minimum area of divergent region.
int minArea = Integer.MAX_VALUE;
FolderShape closestShape = null;
for (FolderShape shape : getAllShapes()) {
shapePath.reset();
shape.addShape(shapePath, 0, 0, size / 2f);
shapeR.setPath(shapePath, full);
shapeR.op(iconR, Op.XOR);
RegionIterator itr = new RegionIterator(shapeR);
int area = 0;
while (itr.next(tempRect)) {
area += tempRect.width() * tempRect.height();
}
if (area < minArea) {
minArea = area;
closestShape = shape;
}
}
if (closestShape != null) {
FolderShape shape = closestShape;
new MainThreadExecutor().execute(() -> updateFolderShape(shape));
}
}
private static void updateFolderShape(FolderShape shape) {
sInstance = shape;
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) {
return;
}
Launcher launcher = (Launcher) app.getModel().getCallback();
if (launcher != null) {
launcher.getWorkspace().mapOverItems(MAP_NO_RECURSE, (i, v) -> {
if (v instanceof FolderIcon) {
v.invalidate();
}
return false;
});
}
}
}
@@ -16,6 +16,8 @@
package com.android.launcher3.folder;
import static com.android.launcher3.folder.FolderShape.getShape;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -49,16 +51,6 @@ public class PreviewBackground {
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
private final PorterDuffXfermode mClipPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
// Create a RadialGradient such that it draws a black circle and then extends with
// transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
// just at the edge quickly change it to transparent.
private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
new float[] {0, 0.999f, 1},
Shader.TileMode.CLAMP);
private final PorterDuffXfermode mShadowPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
private RadialGradient mShadowShader = null;
@@ -208,8 +200,7 @@ public class PreviewBackground {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(getBgColor());
drawCircle(canvas, 0 /* deltaRadius */);
getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
drawShadow(canvas);
}
@@ -244,7 +235,7 @@ public class PreviewBackground {
mPaint.setShader(null);
if (canvas.isHardwareAccelerated()) {
mPaint.setXfermode(mShadowPorterDuffXfermode);
canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint);
mPaint.setXfermode(null);
}
@@ -287,7 +278,10 @@ public class PreviewBackground {
mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
drawCircle(canvas, 1 /* deltaRadius */);
float inset = 1f;
getShape().drawShape(canvas,
getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint);
}
public void drawLeaveBehind(Canvas canvas) {
@@ -296,40 +290,17 @@ public class PreviewBackground {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.argb(160, 245, 245, 245));
drawCircle(canvas, 0 /* deltaRadius */);
getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
mScale = originalScale;
}
private void drawCircle(Canvas canvas,float deltaRadius) {
float radius = getScaledRadius();
canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
radius - deltaRadius, mPaint);
}
public Path getClipPath() {
mPath.reset();
float r = getScaledRadius();
mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
return mPath;
}
// It is the callers responsibility to save and restore the canvas layers.
void clipCanvasHardware(Canvas canvas) {
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(mClipPorterDuffXfermode);
float radius = getScaledRadius();
mShaderMatrix.setScale(radius, radius);
mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
mClipShader.setLocalMatrix(mShaderMatrix);
mPaint.setShader(mClipShader);
canvas.drawPaint(mPaint);
mPaint.setXfermode(null);
mPaint.setShader(null);
}
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
if (mDrawingDelegate != delegate) {
delegate.addFolderBackground(this);
@@ -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}.
@@ -261,7 +261,7 @@ public class BaseIconCache {
protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
info.title = Utilities.trim(entry.title);
info.contentDescription = entry.contentDescription;
((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry);
}
public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
@@ -69,14 +69,14 @@ public class BaseIconFactory {
public ShadowGenerator getShadowGenerator() {
if (mShadowGenerator == null) {
mShadowGenerator = new ShadowGenerator(mContext);
mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
}
return mShadowGenerator;
}
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
mNormalizer = new IconNormalizer(mContext);
mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
}
return mNormalizer;
}
@@ -18,8 +18,6 @@ package com.android.launcher3.icons;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import com.android.launcher3.ItemInfoWithIcon;
public class BitmapInfo {
public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
@@ -27,11 +25,6 @@ public class BitmapInfo {
public Bitmap icon;
public int color;
public void applyTo(ItemInfoWithIcon info) {
info.iconBitmap = icon;
info.iconColor = color;
}
public void applyTo(BitmapInfo info) {
info.icon = icon;
info.color = color;
@@ -139,7 +139,7 @@ public class IconCache extends BaseIconCache {
// null info means not installed, but if we have a component from the intent then
// we should still look in the cache for restored app icons.
if (info.getTargetComponent() == null) {
getDefaultIcon(info.user).applyTo(info);
info.applyFrom(getDefaultIcon(info.user));
info.title = "";
info.contentDescription = "";
} else {
@@ -28,14 +28,9 @@ import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import java.nio.ByteBuffer;
import androidx.annotation.NonNull;
@@ -84,9 +79,9 @@ public class IconNormalizer {
private final Matrix mMatrix;
/** package private **/
IconNormalizer(Context context) {
IconNormalizer(Context context, int iconBitmapSize) {
// Use twice the icon size as maximum size to avoid scaling down twice.
mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
mMaxSize = iconBitmapSize * 2;
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
mCanvas = new Canvas(mBitmap);
mPixels = new byte[mMaxSize * mMaxSize];
@@ -193,17 +188,13 @@ public class IconNormalizer {
*/
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
@Nullable Path path, @Nullable boolean[] outMaskShape) {
if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
if (outBounds != null) {
outBounds.set(mAdaptiveIconBounds);
}
return mAdaptiveIconScale;
}
if (d instanceof FolderAdaptiveIcon) {
// Since we just want the scale, avoid heavy drawing operations
d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
}
}
int width = d.getIntrinsicWidth();
int height = d.getIntrinsicHeight();
@@ -314,7 +305,7 @@ public class IconNormalizer {
float areaScale = area / (width * height);
// Use sqrt of the final ratio as the images is scaled across both width and height.
float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
mAdaptiveIconScale = scale;
mAdaptiveIconBounds.set(mBounds);
@@ -16,7 +16,6 @@
package com.android.launcher3.icons;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BlurMaskFilter;
@@ -28,23 +27,19 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import com.android.launcher3.LauncherAppState;
import androidx.core.graphics.ColorUtils;
/**
* Utility class to add shadows to bitmaps.
*/
public class ShadowGenerator {
// Percent of actual icon size
private static final float HALF_DISTANCE = 0.5f;
public static final float BLUR_FACTOR = 0.5f/48;
// Percent of actual icon size
public static final float KEY_SHADOW_DISTANCE = 1f/48;
private static final int KEY_SHADOW_ALPHA = 61;
// Percent of actual icon size
private static final float HALF_DISTANCE = 0.5f;
private static final int AMBIENT_SHADOW_ALPHA = 30;
private final int mIconSize;
@@ -53,8 +48,8 @@ public class ShadowGenerator {
private final Paint mDrawPaint;
private final BlurMaskFilter mDefaultBlurMaskFilter;
public ShadowGenerator(Context context) {
mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
public ShadowGenerator(int iconSize) {
mIconSize = iconSize;
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
@@ -153,7 +153,7 @@ public class LoaderCursor extends CursorWrapper {
info.title = getTitle();
// the fallback icon
if (!loadIcon(info)) {
mIconCache.getDefaultIcon(info.user).applyTo(info);
info.applyFrom(mIconCache.getDefaultIcon(info.user));
}
// TODO: If there's an explicit component and we can't install that, delete it.
@@ -176,7 +176,7 @@ public class LoaderCursor extends CursorWrapper {
BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
li.recycle();
if (iconInfo != null) {
iconInfo.applyTo(info);
info.applyFrom(iconInfo);
return true;
}
}
@@ -185,7 +185,7 @@ public class LoaderCursor extends CursorWrapper {
// Failed to load from resource, try loading from DB.
byte[] data = getBlob(iconIndex);
try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info);
info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)));
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to load icon for info " + info, e);
@@ -498,8 +498,8 @@ public class LoaderTask implements Runnable {
c.loadIcon(finalInfo) ? finalInfo.iconBitmap : null;
LauncherIcons li = LauncherIcons.obtain(context);
li.createShortcutIcon(pinnedShortcut,
true /* badged */, fallbackIconProvider).applyTo(info);
info.applyFrom(li.createShortcutIcon(pinnedShortcut,
true /* badged */, fallbackIconProvider));
li.recycle();
if (pmHelper.isAppSuspended(
pinnedShortcut.getPackage(), info.user)) {
@@ -194,7 +194,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
li.recycle();
if (iconInfo != null) {
iconInfo.applyTo(si);
si.applyFrom(iconInfo);
infoUpdated = true;
}
}
@@ -96,8 +96,8 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask {
// If the shortcut is pinned but no longer has an icon in the system,
// keep the current icon instead of reverting to the default icon.
LauncherIcons li = LauncherIcons.obtain(context);
li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap))
.applyTo(shortcutInfo);
shortcutInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
Provider.of(shortcutInfo.iconBitmap)));
li.recycle();
updatedShortcutInfos.add(shortcutInfo);
}
@@ -95,7 +95,7 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask {
// If the shortcut is pinned but no longer has an icon in the system,
// keep the current icon instead of reverting to the default icon.
LauncherIcons li = LauncherIcons.obtain(context);
li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si);
si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)));
li.recycle();
} else {
si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
@@ -150,7 +150,7 @@ public class PopupPopulator {
final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
// Use unbadged icon for the menu.
LauncherIcons li = LauncherIcons.obtain(launcher);
li.createShortcutIcon(shortcut, false /* badged */).applyTo(si);
si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */));
li.recycle();
si.rank = i;
@@ -0,0 +1,72 @@
/*
* 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.popup;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.userevent.nano.LauncherLogProto;
public class RemoteActionShortcut extends SystemShortcut<Launcher> {
private static final String TAG = "RemoteActionShortcut";
private final RemoteAction mAction;
public RemoteActionShortcut(RemoteAction action) {
super(action.getIcon(), action.getTitle(), action.getContentDescription(),
R.id.action_remote_action_shortcut);
mAction = action;
}
@Override
public View.OnClickListener getOnClickListener(
final Launcher launcher, final ItemInfo itemInfo) {
return view -> {
AbstractFloatingView.closeAllOpenViews(launcher);
try {
mAction.getActionIntent().send(0,
(pendingIntent, intent, resultCode, resultData, resultExtras) -> {
if (resultData != null && !resultData.isEmpty()) {
Log.e(TAG, "Remote action returned result: " + mAction.getTitle()
+ " : " + resultData);
Toast.makeText(launcher, resultData, Toast.LENGTH_SHORT).show();
}
}, new Handler(Looper.getMainLooper()));
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Remote action canceled: " + mAction.getTitle(), e);
Toast.makeText(launcher, launcher.getString(
R.string.remote_action_failed,
mAction.getTitle()),
Toast.LENGTH_SHORT)
.show();
}
launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
};
}
}
@@ -6,8 +6,10 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
@@ -36,7 +38,7 @@ import java.util.List;
public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
private final int mIconResId;
private final int mLabelResId;
private final Drawable mIcon;
private final Icon mIcon;
private final CharSequence mLabel;
private final CharSequence mContentDescription;
private final int mAccessibilityActionId;
@@ -50,7 +52,7 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite
mContentDescription = null;
}
public SystemShortcut(Drawable icon, CharSequence label, CharSequence contentDescription,
public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
int accessibilityActionId) {
mIcon = icon;
mLabel = label;
@@ -71,7 +73,9 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite
public void setIconAndLabelFor(View iconView, TextView labelView) {
if (mIcon != null) {
iconView.setBackground(mIcon);
mIcon.loadDrawableAsync(iconView.getContext(),
iconView::setBackground,
new Handler(Looper.getMainLooper()));
} else {
iconView.setBackgroundResource(mIconResId);
}
@@ -85,7 +89,9 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite
public void setIconAndContentDescriptionFor(ImageView view) {
if (mIcon != null) {
view.setImageDrawable(mIcon);
mIcon.loadDrawableAsync(view.getContext(),
view::setImageDrawable,
new Handler(Looper.getMainLooper()));
} else {
view.setImageResource(mIconResId);
}
@@ -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);
}
}
}
@@ -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();
}
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}