Snap for 5085055 from 17170c698c to qt-release
Change-Id: I0602e34b126f1d0862e44582a303776845d639bc
This commit is contained in:
+20
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
adamcohen@google.com
|
||||
hyunyoungs@google.com
|
||||
sunnygoyal@google.com
|
||||
winsonc@google.com
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user