Merge "Make it possible to set a bottom margin on the task thumbnail view." into ub-launcher3-master
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 8bd9dba..bce4e0f 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -173,25 +173,22 @@
mCanvas.setBitmap(null);
}
- final Bitmap result;
- if (user != null && !Process.myUserHandle().equals(user)) {
+ if (isInstantApp) {
+ badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+ }
+ if (user != null) {
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
Drawable badged = mPm.getUserBadgedIcon(drawable, user);
if (badged instanceof BitmapDrawable) {
- result = ((BitmapDrawable) badged).getBitmap();
+ bitmap = ((BitmapDrawable) badged).getBitmap();
} else {
- result = createIconBitmap(badged, 1f);
+ bitmap = createIconBitmap(badged, 1f);
}
- } else if (isInstantApp) {
- badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
- result = bitmap;
- } else {
- result = bitmap;
}
- int color = extractColor(result);
+ int color = extractColor(bitmap);
return icon instanceof BitmapInfo.Extender
- ? ((BitmapInfo.Extender) icon).getExtendedInfo(result, color, this)
- : BitmapInfo.of(result, color);
+ ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this)
+ : BitmapInfo.of(bitmap, color);
}
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
new file mode 100644
index 0000000..70a765a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<com.android.launcher3.uioverrides.PredictedAppIcon style="@style/BaseIcon.Workspace" />
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
index fe9fd60..17a3d91 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import android.animation.Animator;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
@@ -40,10 +41,9 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ComponentKey;
@@ -81,12 +81,14 @@
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
+ private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
+
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mAllAppsStore = mLauncher.getAppsView().getAppsStore();
mAllAppsStore.addUpdateListener(this);
- mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false));
+ mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
@@ -102,16 +104,17 @@
mLauncher.getDragController().removeDragListener(this);
}
- /**
- * Fills gaps in the hotseat with predictions
- */
- public void fillGapsWithPrediction(boolean animate) {
+ private void fillGapsWithPrediction() {
+ fillGapsWithPrediction(false, null);
+ }
+
+ private void fillGapsWithPrediction(boolean animate, Runnable callback) {
if (mDragObject != null) {
return;
}
List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
int predictionIndex = 0;
- ArrayList<ItemInfo> newItemsToAdd = new ArrayList<>();
+ ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
View child = mHotseat.getChildAt(
mHotseat.getCellXFromOrder(rank),
@@ -130,21 +133,37 @@
WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
if (isPredictedIcon(child)) {
- BubbleTextView icon = (BubbleTextView) child;
+ PredictedAppIcon icon = (PredictedAppIcon) child;
icon.applyFromWorkspaceItem(predictedItem);
+ icon.finishBinding();
} else {
- newItemsToAdd.add(predictedItem);
+ newItems.add(predictedItem);
}
preparePredictionInfo(predictedItem, rank);
}
- mLauncher.bindItems(newItemsToAdd, animate);
- for (BubbleTextView icon : getPredictedIcons()) {
- icon.verifyHighRes();
- icon.setOnLongClickListener((v) -> {
- PopupContainerWithArrow.showForIcon((BubbleTextView) v);
- return true;
+ bindItems(newItems, animate, callback);
+ }
+
+ private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
+ AnimatorSet animationSet = new AnimatorSet();
+ for (WorkspaceItemInfo item : itemsToAdd) {
+ PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
+ mLauncher.getWorkspace().addInScreenFromBind(icon, item);
+ icon.finishBinding();
+ if (animate) {
+ animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
+ }
+ }
+ if (animate) {
+ animationSet.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (callback != null) callback.run();
+ }
});
- icon.setBackgroundResource(R.drawable.predicted_icon_background);
+ animationSet.start();
+ } else {
+ if (callback != null) callback.run();
}
}
@@ -179,6 +198,7 @@
.build());
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
+
mAppPredictor.requestPredictionUpdate();
}
@@ -210,7 +230,7 @@
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
updateDependencies();
- fillGapsWithPrediction(false);
+ fillGapsWithPrediction();
}
private void updateDependencies() {
@@ -219,7 +239,7 @@
}
private void pinPrediction(ItemInfo info) {
- BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt(
+ PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
mHotseat.getCellXFromOrder(info.rank),
mHotseat.getCellYFromOrder(info.rank));
if (icon == null) {
@@ -230,9 +250,7 @@
LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
- icon.reset();
- icon.applyFromWorkspaceItem(workspaceItemInfo);
- icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+ icon.pin(workspaceItemInfo);
AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN);
}
@@ -265,37 +283,35 @@
return predictedApps;
}
- private List<BubbleTextView> getPredictedIcons() {
- List<BubbleTextView> icons = new ArrayList<>();
+ private List<PredictedAppIcon> getPredictedIcons() {
+ List<PredictedAppIcon> icons = new ArrayList<>();
ViewGroup vg = mHotseat.getShortcutsAndWidgets();
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
if (isPredictedIcon(child)) {
- icons.add((BubbleTextView) child);
+ icons.add((PredictedAppIcon) child);
}
}
return icons;
}
- private void removePredictedApps(boolean animate) {
- for (BubbleTextView icon : getPredictedIcons()) {
- if (animate) {
- icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (icon.getParent() != null) {
- mHotseat.removeView(icon);
- }
+ private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines) {
+ for (PredictedAppIcon icon : getPredictedIcons()) {
+ int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
+ outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
+ mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
+ icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (icon.getParent() != null) {
+ mHotseat.removeView(icon);
}
- });
- } else {
- if (icon.getParent() != null) {
- mHotseat.removeView(icon);
}
- }
+ });
}
}
+
private void notifyItemAction(AppTarget target, int action) {
if (mAppPredictor != null) {
mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build());
@@ -304,8 +320,13 @@
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
- removePredictedApps(true);
+ removePredictedApps(mOutlineDrawings);
mDragObject = dragObject;
+ if (mOutlineDrawings.isEmpty()) return;
+ for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+ mHotseat.addDelegatedCellDrawing(outlineDrawing);
+ }
+ mHotseat.invalidate();
}
@Override
@@ -322,7 +343,14 @@
}
}
mDragObject = null;
- fillGapsWithPrediction(true);
+ fillGapsWithPrediction(true, () -> {
+ if (mOutlineDrawings.isEmpty()) return;
+ for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+ mHotseat.removeDelegatedCellDrawing(outlineDrawing);
+ }
+ mHotseat.invalidate();
+ mOutlineDrawings.clear();
+ });
}
@Nullable
@@ -351,8 +379,7 @@
@Override
public void onAppsUpdated() {
- updateDependencies();
- fillGapsWithPrediction(false);
+ fillGapsWithPrediction();
}
@Override
@@ -375,7 +402,7 @@
}
private static boolean isPredictedIcon(View view) {
- return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo
+ return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
@@ -396,9 +423,8 @@
private static AppTarget getAppTargetFromItemInfo(ItemInfo info) {
if (info.getTargetComponent() == null) return null;
- return new AppTarget.Builder(
- new AppTargetId("app:" + info.getTargetComponent().getPackageName()),
- info.getTargetComponent().getPackageName(), info.user).setClassName(
- info.getTargetComponent().getClassName()).build();
+ ComponentName cn = info.getTargetComponent();
+ return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
+ cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
new file mode 100644
index 0000000..e41c75a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static com.android.launcher3.graphics.IconShape.getShape;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.touch.ItemLongClickListener;
+
+/**
+ * A BubbleTextView with a ring around it's drawable
+ */
+public class PredictedAppIcon extends BubbleTextView {
+
+ private static final float RING_EFFECT_RATIO = 0.12f;
+
+ private DeviceProfile mDeviceProfile;
+ private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private boolean mIsPinned = false;
+ private int mNormalizedIconRadius;
+
+
+ public PredictedAppIcon(Context context) {
+ this(context, null, 0);
+ }
+
+ public PredictedAppIcon(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+ mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
+ setOnClickListener(ItemClickHandler.INSTANCE);
+ setOnFocusChangeListener(Launcher.getLauncher(context).mFocusHandler);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int count = canvas.save();
+ if (!mIsPinned) {
+ drawEffect(canvas);
+ canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
+ canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
+ }
+ super.onDraw(canvas);
+ canvas.restoreToCount(count);
+ }
+
+ @Override
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
+ super.applyFromWorkspaceItem(info);
+ int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
+ mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
+ }
+
+ /**
+ * Removes prediction ring from app icon
+ */
+ public void pin(WorkspaceItemInfo info) {
+ if (mIsPinned) return;
+ applyFromWorkspaceItem(info);
+ setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+ mIsPinned = true;
+ invalidate();
+ }
+
+ /**
+ * prepares prediction icon for usage after bind
+ */
+ public void finishBinding() {
+ setOnLongClickListener((v) -> {
+ PopupContainerWithArrow.showForIcon((BubbleTextView) v);
+ if (getParent() != null) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ return true;
+ });
+ setTextVisibility(false);
+ verifyHighRes();
+ }
+
+ @Override
+ public void getIconBounds(Rect outBounds) {
+ super.getIconBounds(outBounds);
+ if (!mIsPinned) {
+ int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO);
+ outBounds.inset(predictionInset, predictionInset);
+ }
+ }
+
+ private int getOutlineOffsetX() {
+ return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
+ }
+
+ private int getOutlineOffsetY() {
+ return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ }
+
+ private void drawEffect(Canvas canvas) {
+ getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(),
+ mNormalizedIconRadius, mIconRingPaint);
+ }
+
+ /**
+ * Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo
+ */
+ public static PredictedAppIcon createIcon(ViewGroup parent, WorkspaceItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.predicted_app_icon, parent, false);
+ icon.applyFromWorkspaceItem(info);
+ return icon;
+ }
+
+ /**
+ * Draws Predicted Icon outline on cell layout
+ */
+ public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
+
+ private int mOffsetX;
+ private int mOffsetY;
+ private int mIconRadius;
+ private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
+ mDelegateCellX = cellX;
+ mDelegateCellY = cellY;
+ mOffsetX = icon.getOutlineOffsetX();
+ mOffsetY = icon.getOutlineOffsetY();
+ mIconRadius = icon.mNormalizedIconRadius;
+ mOutlinePaint.setStyle(Paint.Style.STROKE);
+ mOutlinePaint.setStrokeWidth(5);
+ mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0));
+ mOutlinePaint.setColor(Color.argb(100, 245, 245, 245));
+ }
+
+ /**
+ * Draws predicted app icon outline under CellLayout
+ */
+ @Override
+ public void drawUnderItem(Canvas canvas) {
+ getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
+ }
+
+ /**
+ * Draws PredictedAppIcon outline over CellLayout
+ */
+ @Override
+ public void drawOverItem(Canvas canvas) {
+ // Does nothing
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 71f8ba4..07537ed 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -391,12 +391,11 @@
@Override
public void onDestroy() {
- PluginManagerWrapper.INSTANCE.get(getBaseContext()).removePluginListener(this);
-
sIsInitialized = false;
if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
+ PluginManagerWrapper.INSTANCE.get(getBaseContext()).removePluginListener(this);
}
disposeEventHandlers();
mDeviceState.destroy();
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index bc658e4..662b86e 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -15,9 +15,6 @@
-->
<resources>
- <!-- Container -->
- <item name="container_margin" format="fraction" type="fraction">12%</item>
-
<!-- Fast scroll -->
<dimen name="fastscroll_popup_width">58dp</dimen>
<dimen name="fastscroll_popup_height">48dp</dimen>
diff --git a/res/values/config.xml b/res/values/config.xml
index 2a1f6f7..0dfed97 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -114,4 +114,7 @@
<!-- Recents -->
<item type="id" name="overview_panel"/>
+
+ <!-- Configuration resources -->
+ <array name="dynamic_resources"> </array>
</resources>
diff --git a/res/xml/dynamic_resources.xml b/res/xml/dynamic_resources.xml
new file mode 100644
index 0000000..f5d2628
--- /dev/null
+++ b/res/xml/dynamic_resources.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<DynamicResources>
+ <entry id="@color/delete_target_hover_tint" />
+ <entry id="@color/delete_target_hover_tint" />
+ <entry id="@color/delete_target_hover_tint" />
+ <entry id="@color/delete_target_hover_tint" />
+
+
+</DynamicResources>
\ No newline at end of file
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 976ccd5..89bec98 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -110,7 +110,7 @@
private OnTouchListener mInterceptTouchListener;
- private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
+ private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
@@ -219,8 +219,8 @@
mPreviousReorderDirection[0] = INVALID_DIRECTION;
mPreviousReorderDirection[1] = INVALID_DIRECTION;
- mFolderLeaveBehind.delegateCellX = -1;
- mFolderLeaveBehind.delegateCellY = -1;
+ mFolderLeaveBehind.mDelegateCellX = -1;
+ mFolderLeaveBehind.mDelegateCellY = -1;
setAlwaysDrawnWithCacheEnabled(false);
final Resources res = getResources();
@@ -466,21 +466,18 @@
}
}
- for (int i = 0; i < mFolderBackgrounds.size(); i++) {
- PreviewBackground bg = mFolderBackgrounds.get(i);
- cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
+ for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
+ DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
+ cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackground(canvas);
- if (!bg.isClipping) {
- bg.drawBackgroundStroke(canvas);
- }
+ cellDrawing.drawUnderItem(canvas);
canvas.restore();
}
- if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) {
- cellToPoint(mFolderLeaveBehind.delegateCellX,
- mFolderLeaveBehind.delegateCellY, mTempLocation);
+ if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
+ cellToPoint(mFolderLeaveBehind.mDelegateCellX,
+ mFolderLeaveBehind.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
mFolderLeaveBehind.drawLeaveBehind(canvas);
@@ -492,23 +489,28 @@
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- for (int i = 0; i < mFolderBackgrounds.size(); i++) {
- PreviewBackground bg = mFolderBackgrounds.get(i);
- if (bg.isClipping) {
- cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
- canvas.save();
- canvas.translate(mTempLocation[0], mTempLocation[1]);
- bg.drawBackgroundStroke(canvas);
- canvas.restore();
- }
+ for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
+ DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
+ cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
+ canvas.save();
+ canvas.translate(mTempLocation[0], mTempLocation[1]);
+ bg.drawOverItem(canvas);
+ canvas.restore();
}
}
- public void addFolderBackground(PreviewBackground bg) {
- mFolderBackgrounds.add(bg);
+ /**
+ * Add Delegated cell drawing
+ */
+ public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
+ mDelegatedCellDrawings.add(bg);
}
- public void removeFolderBackground(PreviewBackground bg) {
- mFolderBackgrounds.remove(bg);
+
+ /**
+ * Remove item from DelegatedCellDrawings
+ */
+ public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
+ mDelegatedCellDrawings.remove(bg);
}
public void setFolderLeaveBehindCell(int x, int y) {
@@ -516,14 +518,14 @@
mFolderLeaveBehind.setup(getContext(), mActivity, null,
child.getMeasuredWidth(), child.getPaddingTop());
- mFolderLeaveBehind.delegateCellX = x;
- mFolderLeaveBehind.delegateCellY = y;
+ mFolderLeaveBehind.mDelegateCellX = x;
+ mFolderLeaveBehind.mDelegateCellY = y;
invalidate();
}
public void clearFolderLeaveBehind() {
- mFolderLeaveBehind.delegateCellX = -1;
- mFolderLeaveBehind.delegateCellY = -1;
+ mFolderLeaveBehind.mDelegateCellX = -1;
+ mFolderLeaveBehind.mDelegateCellY = -1;
invalidate();
}
@@ -2744,6 +2746,24 @@
}
/**
+ * A Delegated cell Drawing for drawing on CellLayout
+ */
+ public abstract static class DelegatedCellDrawing {
+ public int mDelegateCellX;
+ public int mDelegateCellY;
+
+ /**
+ * Draw under CellLayout
+ */
+ public abstract void drawUnderItem(Canvas canvas);
+
+ /**
+ * Draw over CellLayout
+ */
+ public abstract void drawOverItem(Canvas canvas);
+ }
+
+ /**
* Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
* if necessary).
*/
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 4e0f2e7..52a393f 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -21,14 +21,19 @@
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.View;
+import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.util.UiThreadHelper;
+import java.util.List;
+
/**
* The edit text that reports back when the back key has been pressed.
+ * Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
*/
public class ExtendedEditText extends EditText {
@@ -85,12 +90,9 @@
super.onLayout(changed, left, top, right, bottom);
if (mShowImeAfterFirstLayout) {
// soft input only shows one frame after the layout of the EditText happens,
- post(new Runnable() {
- @Override
- public void run() {
- showSoftInput();
- mShowImeAfterFirstLayout = false;
- }
+ post(() -> {
+ showSoftInput();
+ mShowImeAfterFirstLayout = false;
});
}
}
@@ -103,9 +105,27 @@
UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
}
+ @Override
+ public void onCommitCompletion(CompletionInfo text) {
+ setText(text.getText());
+ }
+
+ /**
+ * Currently only used for folder name suggestion.
+ */
+ public void displayCompletions(List<String> suggestList) {
+ int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
+ CompletionInfo[] cInfo = new CompletionInfo[cnt];
+ for (int i = 0; i < cnt; i++) {
+ cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
+ }
+ post(() -> getContext().getSystemService(InputMethodManager.class)
+ .displayCompletions(this, cInfo));
+ }
+
private boolean showSoftInput() {
return requestFocus() &&
- ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
+ getContext().getSystemService(InputMethodManager.class)
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 0bd2c9a..33da582 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -84,6 +84,7 @@
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -217,7 +218,7 @@
& ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
& ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
- mFolderName.forceDisableSuggestions(true);
+ mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get());
mFooter = findViewById(R.id.folder_footer);
@@ -412,19 +413,20 @@
});
}
-
/**
* Show suggested folder title.
*/
- public void showSuggestedTitle(CharSequence suggestName) {
+ public void showSuggestedTitle(String[] suggestName) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
- if (!TextUtils.isEmpty(suggestName)) {
- mFolderName.setHint(suggestName);
- mFolderName.setText(suggestName);
+ if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
+ mFolderName.setHint(suggestName[0]);
+ mFolderName.setText(suggestName[0]);
+ mInfo.title = suggestName[0];
+ animateOpen();
mFolderName.showKeyboard();
- mInfo.title = suggestName;
+ mFolderName.displayCompletions(
+ Arrays.asList(suggestName).subList(1, suggestName.length));
}
- animateOpen();
}
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index fd6d1e3..7bbd45d 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -371,22 +371,31 @@
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
- String[] suggestedNameOut = new String[1];
+ String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
- .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+ Executors.UI_HELPER_EXECUTOR.post(() -> {
+ mLauncher.getFolderNameProvider().getSuggestedFolderName(
+ getContext(), mInfo.contents, suggestedNameOut);
+ showFinalView(finalIndex, item, suggestedNameOut);
+ });
+ } else {
+ showFinalView(finalIndex, item, suggestedNameOut);
}
- postDelayed(() -> {
- mPreviewItemManager.hidePreviewItem(finalIndex, false);
- mFolder.showItem(item);
- invalidate();
- mFolder.showSuggestedTitle(suggestedNameOut[0]);
- }, DROP_IN_ANIMATION_DURATION);
} else {
addItem(item);
}
}
+ private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
+ String[] suggestedNameOut) {
+ postDelayed(() -> {
+ mPreviewItemManager.hidePreviewItem(finalIndex, false);
+ mFolder.showItem(item);
+ invalidate();
+ mFolder.showSuggestedTitle(suggestedNameOut);
+ }, DROP_IN_ANIMATION_DURATION);
+ }
+
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
WorkspaceItemInfo item;
if (d.dragInfo instanceof AppInfo) {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 0a1221e..37aa815 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -29,6 +29,12 @@
public class FolderNameProvider {
/**
+ * IME usually has up to 3 suggest slots. Adding one as in Launcher, there are folder
+ * name edit box that we can also provide suggestion.
+ */
+ public static final int SUGGEST_MAX = 4;
+
+ /**
* Returns suggested folder name.
*/
public CharSequence getSuggestedFolderName(Context context,
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index b2c0ca7..2d177d2 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -48,7 +48,7 @@
* This object represents a FolderIcon preview background. It stores drawing / measurement
* information, handles drawing, and animation (accept state <--> rest state).
*/
-public class PreviewBackground {
+public class PreviewBackground extends CellLayout.DelegatedCellDrawing {
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
@@ -76,8 +76,6 @@
int basePreviewOffsetY;
private CellLayout mDrawingDelegate;
- public int delegateCellX;
- public int delegateCellY;
// When the PreviewBackground is drawn under an icon (for creating a folder) the border
// should not occlude the icon
@@ -124,6 +122,27 @@
}
};
+ /**
+ * Draws folder background under cell layout
+ */
+ @Override
+ public void drawUnderItem(Canvas canvas) {
+ drawBackground(canvas);
+ if (!isClipping) {
+ drawBackgroundStroke(canvas);
+ }
+ }
+
+ /**
+ * Draws folder background on cell layout
+ */
+ @Override
+ public void drawOverItem(Canvas canvas) {
+ if (isClipping) {
+ drawBackgroundStroke(canvas);
+ }
+ }
+
public void setup(Context context, ActivityContext activity, View invalidateDelegate,
int availableSpaceX, int topPadding) {
mInvalidateDelegate = invalidateDelegate;
@@ -317,19 +336,19 @@
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
if (mDrawingDelegate != delegate) {
- delegate.addFolderBackground(this);
+ delegate.addDelegatedCellDrawing(this);
}
mDrawingDelegate = delegate;
- delegateCellX = cellX;
- delegateCellY = cellY;
+ mDelegateCellX = cellX;
+ mDelegateCellY = cellY;
invalidate();
}
private void clearDrawingDelegate() {
if (mDrawingDelegate != null) {
- mDrawingDelegate.removeFolderBackground(this);
+ mDrawingDelegate.removeDelegatedCellDrawing(this);
}
mDrawingDelegate = null;
@@ -395,8 +414,8 @@
// is saved and restored at the beginning of the animation, since cancelling the
// existing animation can clear the delgate.
CellLayout cl = mDrawingDelegate;
- int cellX = delegateCellX;
- int cellY = delegateCellY;
+ int cellX = mDelegateCellX;
+ int cellY = mDelegateCellY;
animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
}
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index a9242f9..3668313 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.settings;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -24,28 +26,21 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.ArrayMap;
-import android.util.ArraySet;
+import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.FlagTogglerPrefUi;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-
-import java.util.List;
-import java.util.Set;
-
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceDataStore;
@@ -54,6 +49,16 @@
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.FlagTogglerPrefUi;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
/**
* Dev-build only UI allowing developers to toggle flag settings and plugins.
* See {@link FeatureFlags}.
@@ -154,44 +159,53 @@
PackageManager pm = getContext().getPackageManager();
Set<String> pluginActions = manager.getPluginActions();
- ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
+
+ ArrayMap<Pair<String, String>, ArrayList<Pair<String, ServiceInfo>>> plugins =
+ new ArrayMap<>();
+
+ Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
+ new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
+ .stream()
+ .map(pi -> pi.packageName)
+ .collect(Collectors.toSet());
+
for (String action : pluginActions) {
String name = toName(action);
List<ResolveInfo> result = pm.queryIntentServices(
- new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
+ new Intent(action), MATCH_DISABLED_COMPONENTS);
for (ResolveInfo info : result) {
String packageName = info.serviceInfo.packageName;
- if (!plugins.containsKey(packageName)) {
- plugins.put(packageName, new ArraySet<>());
+ if (!pluginPermissionApps.contains(packageName)) {
+ continue;
}
- plugins.get(packageName).add(name);
+
+ Pair<String, String> key = Pair.create(packageName, info.serviceInfo.processName);
+ if (!plugins.containsKey(key)) {
+ plugins.put(key, new ArrayList<>());
+ }
+ plugins.get(key).add(Pair.create(name, info.serviceInfo));
}
}
- List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
- PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
- PreferenceDataStore enabled = manager.getPluginEnabler();
- apps.forEach(app -> {
- if (!plugins.containsKey(app.packageName)) return;
- SwitchPreference pref = new PluginPreference(prefContext, app, enabled);
- pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
- mPluginsCategory.addPreference(pref);
+ PreferenceDataStore enabler = manager.getPluginEnabler();
+ plugins.forEach((key, si) -> {
+ String packageName = key.first;
+ List<ComponentName> componentNames = si.stream()
+ .map(p -> new ComponentName(packageName, p.second.name))
+ .collect(Collectors.toList());
+ if (!componentNames.isEmpty()) {
+ SwitchPreference pref = new PluginPreference(
+ prefContext, si.get(0).second.applicationInfo, enabler, componentNames);
+ pref.setSummary("Plugins: "
+ + si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
+ mPluginsCategory.addPreference(pref);
+ }
});
}
- private String toString(ArraySet<String> plugins) {
- StringBuilder b = new StringBuilder();
- for (String string : plugins) {
- if (b.length() != 0) {
- b.append(", ");
- }
- b.append(string);
- }
- return b.toString();
- }
-
private String toName(String action) {
- String str = action.replace("com.android.systemui.action.PLUGIN_", "");
+ String str = action.replace("com.android.systemui.action.PLUGIN_", "")
+ .replace("com.android.launcher3.action.PLUGIN_", "");
StringBuilder b = new StringBuilder();
for (String s : str.split("_")) {
if (b.length() != 0) {
@@ -205,18 +219,20 @@
private static class PluginPreference extends SwitchPreference {
private final boolean mHasSettings;
- private final PackageInfo mInfo;
private final PreferenceDataStore mPluginEnabler;
+ private final String mPackageName;
+ private final List<ComponentName> mComponentNames;
- public PluginPreference(Context prefContext, PackageInfo info,
- PreferenceDataStore pluginEnabler) {
+ PluginPreference(Context prefContext, ApplicationInfo info,
+ PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
super(prefContext);
PackageManager pm = prefContext.getPackageManager();
mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
.setPackage(info.packageName), 0) != null;
- mInfo = info;
+ mPackageName = info.packageName;
+ mComponentNames = componentNames;
mPluginEnabler = pluginEnabler;
- setTitle(info.applicationInfo.loadLabel(pm));
+ setTitle(info.loadLabel(pm));
setChecked(isPluginEnabled());
setWidgetLayoutResource(R.layout.switch_preference_with_settings);
}
@@ -227,9 +243,7 @@
}
private boolean isPluginEnabled() {
- for (int i = 0; i < mInfo.services.length; i++) {
- ComponentName componentName = new ComponentName(mInfo.packageName,
- mInfo.services[i].name);
+ for (ComponentName componentName : mComponentNames) {
if (!isEnabled(componentName)) {
return false;
}
@@ -240,17 +254,14 @@
@Override
protected boolean persistBoolean(boolean isEnabled) {
boolean shouldSendBroadcast = false;
- for (int i = 0; i < mInfo.services.length; i++) {
- ComponentName componentName = new ComponentName(mInfo.packageName,
- mInfo.services[i].name);
-
+ for (ComponentName componentName : mComponentNames) {
if (isEnabled(componentName) != isEnabled) {
mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
shouldSendBroadcast = true;
}
}
if (shouldSendBroadcast) {
- final String pkg = mInfo.packageName;
+ final String pkg = mPackageName;
final Intent intent = new Intent(PLUGIN_CHANGED,
pkg != null ? Uri.fromParts("package", pkg, null) : null);
getContext().sendBroadcast(intent);
@@ -268,8 +279,7 @@
: View.GONE);
holder.findViewById(R.id.settings).setOnClickListener(v -> {
ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
- new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
- mInfo.packageName), 0);
+ new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0);
if (result != null) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(result.activityInfo.packageName,
@@ -278,7 +288,7 @@
});
holder.itemView.setOnLongClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.setData(Uri.fromParts("package", mInfo.packageName, null));
+ intent.setData(Uri.fromParts("package", mPackageName, null));
getContext().startActivity(intent);
return true;
});
diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java
new file mode 100644
index 0000000..8a75767
--- /dev/null
+++ b/src/com/android/launcher3/util/DynamicResource.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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.content.Context;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.DimenRes;
+import androidx.annotation.FractionRes;
+import androidx.annotation.IntegerRes;
+
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.ResourceProvider;
+
+/**
+ * Utility class to support customizing resource values using plugins
+ *
+ * To load resources, call
+ * DynamicResource.provider(context).getInt(resId) or any other supported methods
+ *
+ * To allow customization for a particular resource, add them to dynamic_resources.xml
+ */
+public class DynamicResource implements ResourceProvider, PluginListener<ResourceProvider> {
+
+ private static final MainThreadInitializedObject<DynamicResource> INSTANCE =
+ new MainThreadInitializedObject<>(DynamicResource::new);
+
+ private final Context mContext;
+ private ResourceProvider mPlugin;
+
+ private DynamicResource(Context context) {
+ mContext = context;
+ PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this,
+ ResourceProvider.class, false /* allowedMultiple */);
+ }
+
+ @Override
+ public int getInt(@IntegerRes int resId) {
+ return mContext.getResources().getInteger(resId);
+ }
+
+ @Override
+ public float getFraction(@FractionRes int resId) {
+ return mContext.getResources().getFraction(resId, 1, 1);
+ }
+
+ @Override
+ public float getDimension(@DimenRes int resId) {
+ return mContext.getResources().getDimension(resId);
+ }
+
+ @Override
+ public int getColor(@ColorRes int resId) {
+ return mContext.getResources().getColor(resId, null);
+ }
+
+ @Override
+ public void onPluginConnected(ResourceProvider plugin, Context context) {
+ mPlugin = plugin;
+ }
+
+ @Override
+ public void onPluginDisconnected(ResourceProvider plugin) {
+ mPlugin = null;
+ }
+
+ /**
+ * Returns the currently active or default provider
+ */
+ public static ResourceProvider provider(Context context) {
+ DynamicResource dr = DynamicResource.INSTANCE.get(context);
+ ResourceProvider plugin = dr.mPlugin;
+ return plugin == null ? dr : plugin;
+ }
+}
diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java
new file mode 100644
index 0000000..eaed9e7
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Plugin to support customizing resource
+ */
+@ProvidesInterface(action = ResourceProvider.ACTION, version = ResourceProvider.VERSION)
+public interface ResourceProvider extends Plugin {
+ String ACTION = "com.android.launcher3.action.PLUGIN_DYNAMIC_RESOURCE";
+ int VERSION = 1;
+
+ /**
+ * @see android.content.res.Resources#getInteger(int)
+ */
+ int getInt(int resId);
+
+ /**
+ * @see android.content.res.Resources#getFraction(int, int, int)
+ */
+ float getFraction(int resId);
+
+ /**
+ * @see android.content.res.Resources#getDimension(int)
+ */
+ float getDimension(int resId);
+
+ /**
+ * @see android.content.res.Resources#getColor(int)
+ */
+ int getColor(int resId);
+}