diff options
Diffstat (limited to 'src/com/android/launcher3/widget')
9 files changed, 286 insertions, 274 deletions
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index dcc86a15e9..5171fa218a 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -16,12 +16,12 @@ package com.android.launcher3.widget; import static com.android.app.animation.Interpolators.EMPHASIZED; -import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER; +import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker; +import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_TIP_SEEN; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; @@ -29,54 +29,47 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.WindowInsets; import android.view.animation.Interpolator; -import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.core.view.ViewCompat; +import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; -import com.android.launcher3.DragSource; -import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; -import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; import com.android.launcher3.util.window.WindowManagerProxy; import com.android.launcher3.views.AbstractSlideInView; -import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.ArrowTipView; /** * Base class for various widgets popup */ -public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> - implements OnClickListener, OnLongClickListener, DragSource, +public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity> + implements OnClickListener, OnLongClickListener, PopupDataProvider.PopupDataChangeListener, Insettable, OnDeviceProfileChangeListener { /** The default number of cells that can fit horizontally in a widget sheet. */ public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4; - protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN = - "launcher.widgets_education_tip_seen"; protected final Rect mInsets = new Rect(); - /* Touch handling related member variables. */ - private Toast mWidgetInstructionToast; - @Px protected int mContentHorizontalMargin; @Px protected int mWidgetCellHorizontalPadding; protected int mNavBarScrimHeight; private final Paint mNavBarScrimPaint; + private boolean mDisableNavBarScrim = false; + public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContentHorizontalMargin = getResources().getDimensionPixelSize( @@ -115,36 +108,35 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> mNavBarScrimPaint.setColor(navBarScrimColor); invalidate(); } + setupNavBarColor(); } @Override public final void onClick(View v) { - Object tag = null; if (v instanceof WidgetCell) { - tag = v.getTag(); - } else if (v.getParent() instanceof WidgetCell) { - tag = ((WidgetCell) v.getParent()).getTag(); + mActivityContext.getItemOnClickListener().onClick(v); + } else if (v.getParent() instanceof WidgetCell wc) { + mActivityContext.getItemOnClickListener().onClick(wc); } - if (tag instanceof PendingAddShortcutInfo) { - mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast); - } else { - mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast); - } - } @Override public boolean onLongClick(View v) { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick"); v.cancelLongPress(); - if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false; + boolean result; if (v instanceof WidgetCell) { - return beginDraggingWidget((WidgetCell) v); - } else if (v.getParent() instanceof WidgetCell) { - return beginDraggingWidget((WidgetCell) v.getParent()); + result = mActivityContext.getAllAppsItemLongClickListener().onLongClick(v); + } else if (v.getParent() instanceof WidgetCell wc) { + result = mActivityContext.getAllAppsItemLongClickListener().onLongClick(wc); + } else { + return true; + } + if (result) { + close(true); } - return true; + return result; } @Override @@ -158,8 +150,15 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> } } + /** Enables or disables the sheet's nav bar scrim. */ + public void disableNavBarScrim(boolean disable) { + mDisableNavBarScrim = disable; + } + private int getNavBarScrimHeight(WindowInsets insets) { - if (Utilities.ATLEAST_Q) { + if (mDisableNavBarScrim) { + return 0; + } else if (Utilities.ATLEAST_Q) { return insets.getTappableElementInsets().bottom; } else { return insets.getStableInsetBottom(); @@ -193,14 +192,8 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); int widthUsed; if (deviceProfile.isTablet) { - int margin = deviceProfile.allAppsLeftRightMargin; - if (deviceProfile.isLandscape - && LARGE_SCREEN_WIDGET_PICKER.get() - && !deviceProfile.isTwoPanels) { - margin = getResources().getDimensionPixelSize( - R.dimen.widget_picker_landscape_tablet_left_right_margin); - } - widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right)); + widthUsed = Math.max(2 * getTabletMargin(deviceProfile), + 2 * (mInsets.left + mInsets.right)); } else if (mInsets.bottom > 0) { widthUsed = mInsets.left + mInsets.right; } else { @@ -215,39 +208,16 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> MeasureSpec.getSize(heightMeasureSpec)); } - private boolean beginDraggingWidget(WidgetCell v) { - // Get the widget preview as the drag representation - WidgetImageView image = v.getWidgetView(); - - // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and - // we abort the drag. - if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) { - return false; + private int getTabletMargin(DeviceProfile deviceProfile) { + if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) { + return getResources().getDimensionPixelSize( + R.dimen.widget_picker_landscape_tablet_left_right_margin); } - - PendingItemDragHelper dragHelper = new PendingItemDragHelper(v); - // RemoteViews are being rendered in AppWidgetHostView in WidgetCell. And thus, the scale of - // RemoteViews is equivalent to the AppWidgetHostView scale. - dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview(), v.getAppWidgetHostViewScale()); - dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview()); - - if (image.getDrawable() != null) { - int[] loc = new int[2]; - getPopupContainer().getLocationInDragLayer(image, loc); - - dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(), - image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions()); - } else { - NavigableAppWidgetHostView preview = v.getAppWidgetHostViewPreview(); - int[] loc = new int[2]; - getPopupContainer().getLocationInDragLayer(preview, loc); - Rect r = new Rect(); - preview.getWorkspaceVisualDragBounds(r); - dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(), - new Point(loc[0], loc[1]), this, new DragOptions()); + if (deviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) { + return getResources().getDimensionPixelSize( + R.dimen.widget_picker_two_panels_left_right_margin); } - close(true); - return true; + return deviceProfile.allAppsLeftRightMargin; } @Override @@ -256,14 +226,6 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> ? EMPHASIZED : super.getIdleInterpolator(); } - // - // Drag related handling methods that implement {@link DragSource} interface. - // - - @Override - public void onDropCompleted(View target, DragObject d, boolean success) { } - - protected void onCloseComplete() { super.onCloseComplete(); clearNavBarColor(); @@ -275,48 +237,22 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> } protected void setupNavBarColor() { - boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark); - getSystemUiController().updateUiState( - SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, - isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV); - } + boolean isNavBarDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark); - protected SystemUiController getSystemUiController() { - return mActivityContext.getSystemUiController(); - } - - /** - * Show Widget tap toast prompting user to drag instead - */ - public static Toast showWidgetToast(Context context, Toast toast) { - // Let the user know that they have to long press to add a widget - if (toast != null) { - toast.cancel(); + // In light mode, landscape reverses navbar background color. + boolean isPhoneLandscape = + !mActivityContext.getDeviceProfile().isTablet && mInsets.bottom == 0; + if (!isNavBarDark && isPhoneLandscape) { + isNavBarDark = true; } - CharSequence msg = Utilities.wrapForTts( - context.getText(R.string.long_press_widget_to_add), - context.getString(R.string.long_accessible_way_to_add)); - toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); - toast.show(); - return toast; + getSystemUiController().updateUiState(SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, + isNavBarDark ? SystemUiController.FLAG_DARK_NAV + : SystemUiController.FLAG_LIGHT_NAV); } - /** - * Show shortcut tap toast prompting user to drag instead. - */ - private static Toast showShortcutToast(Context context, Toast toast) { - // Let the user know that they have to long press to add a widget - if (toast != null) { - toast.cancel(); - } - - CharSequence msg = Utilities.wrapForTts( - context.getText(R.string.long_press_shortcut_to_add), - context.getString(R.string.long_accessible_way_to_add_shortcut)); - toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); - toast.show(); - return toast; + protected SystemUiController getSystemUiController() { + return mActivityContext.getSystemUiController(); } /** Shows education tip on top center of {@code view} if view is laid out. */ @@ -333,22 +269,22 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher> /* arrowXCoord= */coords[0] + view.getWidth() / 2, /* yCoord= */coords[1]); if (arrowTipView != null) { - mActivityContext.getSharedPrefs().edit() - .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply(); + LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_TIP_SEEN, true); } return arrowTipView; } /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */ protected boolean hasSeenEducationTip() { - return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false) + return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_TIP_SEEN) || Utilities.isRunningInTestHarness(); } @Override protected void setTranslationShift(float translationShift) { super.setTranslationShift(translationShift); - Launcher launcher = ActivityContext.lookupContext(getContext()); - launcher.onWidgetsTransition(1 - translationShift); + if (mActivityContext instanceof Launcher ls) { + ls.onWidgetsTransition(1 - translationShift); + } } } diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java index 6f74fd965a..99485bec2e 100644 --- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java +++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java @@ -257,8 +257,6 @@ public class DatabaseWidgetPreviewLoader { throw new RuntimeException("Max size is too small for preview"); } return BitmapRenderer.createHardwareBitmap(size, size, c -> { - drawBoxWithShadow(c, size, size); - LauncherIcons li = LauncherIcons.obtain(mContext); Drawable icon = li.createBadgedIconBitmap( mutateOnMainThread(info.getFullResIcon( diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 340a61e1c5..5d069ed264 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -19,24 +19,25 @@ package com.android.launcher3.widget; import android.annotation.TargetApi; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Build; import android.os.Handler; +import android.os.Parcelable; import android.os.SystemClock; import android.os.Trace; import android.util.Log; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.view.MotionEvent; import android.view.View; -import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.AdapterView; import android.widget.Advanceable; import android.widget.RemoteViews; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.CheckLongPressHelper; @@ -63,6 +64,8 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView private static final long ADVANCE_INTERVAL = 20000; private static final long ADVANCE_STAGGER = 250; + private @Nullable CellChildViewPreLayoutListener mCellChildViewPreLayoutListener; + // Maintains a list of widget ids which are supposed to be auto advanced. private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray(); // Maximum duration for which updates can be deferred. @@ -74,9 +77,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView private final CheckLongPressHelper mLongPressHelper; protected final Launcher mLauncher; - @ViewDebug.ExportedProperty(category = "launcher") - private boolean mReinflateOnConfigChange; - // Maintain the color manager. private final LocalColorExtractor mColorExtractor; @@ -171,17 +171,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView // The provider info or the views might have changed. checkIfAutoAdvance(); - - // It is possible that widgets can receive updates while launcher is not in the foreground. - // Consequently, the widgets will be inflated for the orientation of the foreground activity - // (framework issue). On resuming, we ensure that any widgets are inflated for the current - // orientation. - mReinflateOnConfigChange = !isSameOrientation(); - } - - private boolean isSameOrientation() { - return mLauncher.getResources().getConfiguration().orientation == - mLauncher.getOrientation(); } private boolean checkScrollableRecursively(ViewGroup viewGroup) { @@ -335,6 +324,26 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView requestLayout(); } + /** + * Set the pre-layout listener + * @param listener The listener to be notified when {@code CellLayout} is to layout this view + */ + public void setCellChildViewPreLayoutListener( + @NonNull CellChildViewPreLayoutListener listener) { + mCellChildViewPreLayoutListener = listener; + } + + /** @return The current cell layout listener */ + @Nullable + public CellChildViewPreLayoutListener getCellChildViewPreLayoutListener() { + return mCellChildViewPreLayoutListener; + } + + /** Clear the listener for the pre-layout in CellLayout */ + public void clearCellChildViewPreLayoutListener() { + mCellChildViewPreLayoutListener = null; + } + @Override public void onColorsChanged(SparseIntArray colors) { if (isDeferringUpdates()) { @@ -425,17 +434,6 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView scheduleNextAdvance(); } - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - // Only reinflate when the final configuration is same as the required configuration - if (mReinflateOnConfigChange && isSameOrientation()) { - mReinflateOnConfigChange = false; - reInflate(); - } - } - public void reInflate() { if (!isAttachedToWindow()) { return; @@ -460,4 +458,28 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView } return false; } + + /** + * Listener interface to be called when {@code CellLayout} is about to layout this child view + */ + public interface CellChildViewPreLayoutListener { + /** + * Notify the bound changes to this view on pre-layout + * @param v The view which the listener is set for + * @param left The new left coordinate of this view + * @param top The new top coordinate of this view + * @param right The new right coordinate of this view + * @param bottom The new bottom coordinate of this view + */ + void notifyBoundChangeOnPreLayout(View v, int left, int top, int right, int bottom); + } + + @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + try { + super.dispatchRestoreInstanceState(container); + } catch (Exception e) { + Log.i(TAG, "Exception: " + e); + } + } } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java index 10aef9ac66..ef51d152c7 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java @@ -67,7 +67,7 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo */ public int maxSpanY; - private boolean mIsMinSizeFulfilled; + protected boolean mIsMinSizeFulfilled; public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context, AppWidgetProviderInfo info) { diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java index 737cdbd61f..0860e72420 100644 --- a/src/com/android/launcher3/widget/WidgetManagerHelper.java +++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java @@ -57,10 +57,15 @@ public class WidgetManagerHelper { /** * @see AppWidgetManager#getAppWidgetInfo(int) */ - public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) { - if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { - return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId); + public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo( + int appWidgetId, ComponentName componentName) { + + // For custom widgets. + if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID && !CustomWidgetManager + .INSTANCE.get(mContext).getWidgetIdForCustomProvider(componentName).equals("")) { + return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(componentName); } + AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId); return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info); } diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java index 8b3bbce1c5..44571a6c4a 100644 --- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java @@ -33,14 +33,15 @@ import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo implements Parcelable { - public final int providerId; + public final String providerId; - protected CustomAppWidgetProviderInfo(Parcel parcel, boolean readSelf, int providerId) { + protected CustomAppWidgetProviderInfo(Parcel parcel, boolean readSelf, String providerId) { super(parcel); if (readSelf) { - this.providerId = parcel.readInt(); + this.providerId = parcel.readString(); - provider = new ComponentName(parcel.readString(), CLS_CUSTOM_WIDGET_PREFIX + providerId); + provider = new ComponentName(parcel.readString(), + CLS_CUSTOM_WIDGET_PREFIX + parcel.readString()); label = parcel.readString(); initialLayout = parcel.readInt(); @@ -58,7 +59,10 @@ public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo } @Override - public void initSpans(Context context, InvariantDeviceProfile idp) { } + public void initSpans(Context context, InvariantDeviceProfile idp) { + mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns + && Math.min(spanY, minSpanY) <= idp.numRows; + } @Override public String getLabel(PackageManager packageManager) { @@ -73,8 +77,9 @@ public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); - out.writeInt(providerId); + out.writeString(providerId); out.writeString(provider.getPackageName()); + out.writeString(provider.getClassName()); out.writeString(label); out.writeInt(initialLayout); @@ -93,7 +98,7 @@ public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo @Override public CustomAppWidgetProviderInfo createFromParcel(Parcel parcel) { - return new CustomAppWidgetProviderInfo(parcel, true, 0); + return new CustomAppWidgetProviderInfo(parcel, true, ""); } @Override diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java index 2e2a968a44..7cf022175b 100644 --- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java +++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java @@ -16,7 +16,8 @@ package com.android.launcher3.widget.custom; -import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; +import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET; +import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; @@ -24,12 +25,12 @@ import android.content.ComponentName; import android.content.Context; import android.os.Parcel; import android.os.Process; -import android.util.SparseArray; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.R; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.PackageUserKey; @@ -39,8 +40,11 @@ import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.systemui.plugins.CustomWidgetPlugin; import com.android.systemui.plugins.PluginListener; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; @@ -52,24 +56,37 @@ public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE = new MainThreadInitializedObject<>(CustomWidgetManager::new); + private static final String TAG = "CustomWidgetManager"; private final Context mContext; - /** - * auto provider Id is an ever-increasing number that serves as the providerId whenever a new - * custom widget has been connected. - */ - private int mAutoProviderId = 0; - private final SparseArray<CustomWidgetPlugin> mPlugins; + private final HashMap<String, CustomWidgetPlugin> mPlugins; private final List<CustomAppWidgetProviderInfo> mCustomWidgets; - private final SparseArray<ComponentName> mWidgetsIdMap; + private final HashMap<ComponentName, String> mWidgetsIdMap; private Consumer<PackageUserKey> mWidgetRefreshCallback; private CustomWidgetManager(Context context) { mContext = context; - mPlugins = new SparseArray<>(); + mPlugins = new HashMap<>(); mCustomWidgets = new ArrayList<>(); - mWidgetsIdMap = new SparseArray<>(); + mWidgetsIdMap = new HashMap<>(); PluginManagerWrapper.INSTANCE.get(context) .addPluginListener(this, CustomWidgetPlugin.class, true); + + if (SMARTSPACE_AS_A_WIDGET.get()) { + for (String s: context.getResources() + .getStringArray(R.array.custom_widget_providers)) { + try { + Class<?> cls = Class.forName(s); + CustomWidgetPlugin plugin = (CustomWidgetPlugin) + cls.getDeclaredConstructor(Context.class).newInstance(context); + mPlugins.put(plugin.getId(), plugin); + onPluginConnected(mPlugins.get(plugin.getId()), context); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | ClassCastException | NoSuchMethodException + | InvocationTargetException e) { + Log.e(TAG, "Exception found when trying to add custom widgets: " + e); + } + } + } } @Override @@ -79,28 +96,41 @@ public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, @Override public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { - mPlugins.put(mAutoProviderId, plugin); List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context) .getInstalledProvidersForProfile(Process.myUserHandle()); if (providers.isEmpty()) return; Parcel parcel = Parcel.obtain(); providers.get(0).writeToParcel(parcel, 0); parcel.setDataPosition(0); - CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context); + CustomAppWidgetProviderInfo info = newInfo(plugin.getId(), plugin, parcel, context); parcel.recycle(); mCustomWidgets.add(info); - mWidgetsIdMap.put(mAutoProviderId, info.provider); - mWidgetRefreshCallback.accept(null); - mAutoProviderId++; + mWidgetsIdMap.put(info.provider, plugin.getId()); } @Override public void onPluginDisconnected(CustomWidgetPlugin plugin) { - int providerId = findProviderId(plugin); - if (providerId == -1) return; - mPlugins.remove(providerId); - mCustomWidgets.remove(getWidgetProvider(providerId)); - mWidgetsIdMap.remove(providerId); + String providerId = plugin.getId(); + if (mPlugins.containsKey(providerId)) { + mPlugins.remove(providerId); + } + + ComponentName cn = null; + for (Map.Entry entry: mWidgetsIdMap.entrySet()) { + if (entry.getValue().equals(providerId)) { + cn = (ComponentName) entry.getKey(); + } + } + + if (cn != null) { + mWidgetsIdMap.remove(cn); + for (int i = 0; i < mCustomWidgets.size(); i++) { + if (mCustomWidgets.get(i).getComponent().equals(cn)) { + mCustomWidgets.remove(i); + return; + } + } + } } /** @@ -131,12 +161,11 @@ public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, /** * Returns the widget id for a specific provider. */ - public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) { - int index = mWidgetsIdMap.indexOfValue(provider); - if (index >= 0) { - return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index); + public String getWidgetIdForCustomProvider(@NonNull ComponentName provider) { + if (mWidgetsIdMap.containsKey(provider)) { + return mWidgetsIdMap.get(provider); } else { - return AppWidgetManager.INVALID_APPWIDGET_ID; + return ""; } } @@ -144,38 +173,26 @@ public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, * Returns the widget provider in respect to given widget id. */ @Nullable - public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) { - ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId); + public LauncherAppWidgetProviderInfo getWidgetProvider(ComponentName componentName) { for (LauncherAppWidgetProviderInfo info : mCustomWidgets) { - if (info.provider.equals(cn)) return info; + if (info.provider.equals(componentName)) return info; } return null; } - private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin, + private static CustomAppWidgetProviderInfo newInfo(String providerId, CustomWidgetPlugin plugin, Parcel parcel, Context context) { CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo( parcel, false, providerId); - info.provider = new ComponentName( - context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId); - - info.label = plugin.getLabel(); - info.resizeMode = plugin.getResizeMode(); - - info.spanX = plugin.getSpanX(); - info.spanY = plugin.getSpanY(); - info.minSpanX = plugin.getMinSpanX(); - info.minSpanY = plugin.getMinSpanY(); + plugin.updateWidgetInfo(info, context); return info; } - private int findProviderId(CustomWidgetPlugin plugin) { - for (int i = 0; i < mPlugins.size(); i++) { - int providerId = mPlugins.keyAt(i); - if (mPlugins.get(providerId) == plugin) { - return providerId; - } - } - return -1; + /** + * Returns an id to set as the appWidgetId for a custom widget. + */ + public int allocateCustomAppWidgetId(ComponentName componentName) { + return CUSTOM_WIDGET_ID - mCustomWidgets.indexOf(getWidgetProvider(componentName)); } + } diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index a4b605cccd..e9a590b814 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -17,14 +17,14 @@ package com.android.launcher3.widget.picker; import static android.view.View.MeasureSpec.makeMeasureSpec; +import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; -import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER; +import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; import android.animation.Animator; import android.content.Context; -import android.content.pm.LauncherApps; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; @@ -40,6 +40,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; +import android.view.WindowInsetsController; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.Button; @@ -54,8 +55,10 @@ import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.RecyclerView; +import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; @@ -69,7 +72,6 @@ import com.android.launcher3.views.SpringRelativeLayout; import com.android.launcher3.views.StickyHeaderLayout; import com.android.launcher3.views.WidgetsEduView; import com.android.launcher3.widget.BaseWidgetSheet; -import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; @@ -86,27 +88,24 @@ import java.util.stream.IntStream; * Popup for showing the full list of available widgets */ public class WidgetsFullSheet extends BaseWidgetSheet - implements ProviderChangedListener, OnActivePageChangedListener, + implements OnActivePageChangedListener, WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener { private static final long FADE_IN_DURATION = 150; private static final long EDUCATION_TIP_DELAY_MS = 200; private static final long EDUCATION_DIALOG_DELAY_MS = 500; - private static final float VERTICAL_START_POSITION = 0.3f; + // The widget recommendation table can easily take over the entire screen on devices with small // resolution or landscape on phone. This ratio defines the max percentage of content area that // the table can display. private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f; - private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN = - "launcher.widgets_education_dialog_seen"; + private final UserCache mUserCache; private final UserManagerState mUserManagerState = new UserManagerState(); private final UserHandle mCurrentUser = Process.myUserHandle(); private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry -> mCurrentUser.equals(entry.mPkgItem.user); - private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter = - entry -> !mCurrentUser.equals(entry.mPkgItem.user) - && !mUserManagerState.isUserQuiet(entry.mPkgItem.user); + private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter; protected final boolean mHasWorkProfile; protected boolean mHasRecommendedWidgets; protected final SparseArray<AdapterHolder> mAdapters = new SparseArray(); @@ -164,9 +163,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet private boolean mIsInSearchMode; private boolean mIsNoWidgetsViewNeeded; @Px private int mMaxSpanPerRow; - private DeviceProfile mDeviceProfile; - - private int mOrientation; + protected DeviceProfile mDeviceProfile; protected TextView mNoWidgetsView; protected StickyHeaderLayout mSearchScrollView; @@ -179,20 +176,23 @@ public class WidgetsFullSheet extends BaseWidgetSheet public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile(); - mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1; - mOrientation = context.getResources().getConfiguration().orientation; + mDeviceProfile = mActivityContext.getDeviceProfile(); + mUserCache = UserCache.INSTANCE.get(context); + mHasWorkProfile = mUserCache.getUserProfiles() + .stream() + .anyMatch(user -> mUserCache.getUserInfo(user).isWork()); + mWorkWidgetsFilter = entry -> mHasWorkProfile + && mUserCache.getUserInfo(entry.mPkgItem.user).isWork(); mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY)); mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK)); mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH)); Resources resources = getResources(); + mUserManagerState.init(UserCache.INSTANCE.get(context), + context.getSystemService(UserManager.class)); mTabsHeight = mHasWorkProfile ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height) : 0; - - mUserManagerState.init(UserCache.INSTANCE.get(context), - context.getSystemService(UserManager.class)); } public WidgetsFullSheet(Context context, AttributeSet attrs) { @@ -310,7 +310,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) { mNoWidgetsView.setText(R.string.no_search_results); } else if (adapterHolder.mAdapterType == AdapterHolder.WORK - && mUserManagerState.isAnyProfileQuietModeEnabled() + && mUserCache.getUserProfiles().stream() + .filter(userHandle -> mUserCache.getUserInfo(userHandle).isWork()) + .anyMatch(mUserManagerState::isUserQuiet) && mActivityContext.getStringCache() != null) { mNoWidgetsView.setText(mActivityContext.getStringCache().workProfilePausedTitle); } else { @@ -348,15 +350,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mActivityContext.getAppWidgetHolder().addProviderChangeListener(this); - notifyWidgetProvidersChanged(); + LauncherAppState.getInstance(mActivityContext).getModel() + .refreshAndBindWidgetsAndShortcuts(null); onRecommendedWidgetsBound(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mActivityContext.getAppWidgetHolder().removeProviderChangeListener(this); mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView .removeOnAttachStateChangeListener(mBindScrollbarInSearchMode); if (mHasWorkProfile) { @@ -477,11 +478,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet } @Override - public void notifyWidgetProvidersChanged() { - mActivityContext.refreshAndBindWidgetsForPackageUser(null); - } - - @Override public void onWidgetsBound() { if (mIsInSearchMode) { return; @@ -623,7 +619,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (animate) { if (getPopupContainer().getInsets().bottom > 0) { mContent.setAlpha(0); - setTranslationShift(VERTICAL_START_POSITION); } setUpOpenAnimation(mActivityContext.getDeviceProfile().bottomSheetOpenDuration); Animator animator = mOpenCloseAnimation.getAnimationPlayer(); @@ -677,31 +672,27 @@ public class WidgetsFullSheet extends BaseWidgetSheet } /** Shows the {@link WidgetsFullSheet} on the launcher. */ - public static WidgetsFullSheet show(Launcher launcher, boolean animate) { - boolean isTwoPane = LARGE_SCREEN_WIDGET_PICKER.get() - && launcher.getDeviceProfile().isTablet - && launcher.getDeviceProfile().isLandscape - && !launcher.getDeviceProfile().isTwoPanels; - - WidgetsFullSheet sheet; - if (isTwoPane) { - sheet = (WidgetsTwoPaneSheet) launcher.getLayoutInflater().inflate( - R.layout.widgets_two_pane_sheet, - launcher.getDragLayer(), - false); - } else { - sheet = (WidgetsFullSheet) launcher.getLayoutInflater().inflate( - R.layout.widgets_full_sheet, - launcher.getDragLayer(), - false); - } - + public static WidgetsFullSheet show(BaseActivity activity, boolean animate) { + WidgetsFullSheet sheet = (WidgetsFullSheet) activity.getLayoutInflater().inflate( + getWidgetSheetId(activity), + activity.getDragLayer(), + false); sheet.attachToContainer(); sheet.mIsOpen = true; sheet.open(animate); return sheet; } + private static int getWidgetSheetId(BaseActivity activity) { + boolean isTwoPane = (activity.getDeviceProfile().isTablet + && activity.getDeviceProfile().isLandscape + && !activity.getDeviceProfile().isTwoPanels) + // Enables two pane picker for unfolded foldables if the flag is on. + || (activity.getDeviceProfile().isTwoPanels && enableUnfoldedTwoPanePicker()); + + return isTwoPane ? R.layout.widgets_two_pane_sheet : R.layout.widgets_full_sheet; + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return isTouchOnScrollbar(ev) || super.onInterceptTouchEvent(ev); @@ -749,7 +740,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */ @VisibleForTesting - public static WidgetsRecyclerView getWidgetsView(Launcher launcher) { + public static WidgetsRecyclerView getWidgetsView(BaseActivity launcher) { return launcher.findViewById(R.id.primary_widgets_list_view); } @@ -792,18 +783,25 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mIsInSearchMode) { mSearchBar.reset(); } + } - // Checks the orientation of the screen - if (mOrientation != newConfig.orientation) { - mOrientation = newConfig.orientation; - if (LARGE_SCREEN_WIDGET_PICKER.get() - && mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) { - handleClose(false); - show(Launcher.getLauncher(getContext()), false); - } else { - reset(); - } + @Override + public void onDeviceProfileChanged(DeviceProfile dp) { + if (mDeviceProfile.isLandscape != dp.isLandscape && dp.isTablet && !dp.isTwoPanels) { + handleClose(false); + show(BaseActivity.fromContext(getContext()), false); + } else { + reset(); + } + + // When folding/unfolding the foldables, we need to switch between the regular widget picker + // and the two pane picker, so we rebuild the picker with the correct layout. + if (mDeviceProfile.isTwoPanels != dp.isTwoPanels && enableUnfoldedTwoPanePicker()) { + handleClose(false); + show(BaseActivity.fromContext(getContext()), false); } + + mDeviceProfile = dp; } @Override @@ -819,7 +817,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override public void onDragStart(boolean start, float startDisplacement) { super.onDragStart(start, startDisplacement); - getWindowInsetsController().hide(WindowInsets.Type.ime()); + WindowInsetsController insetsController = getWindowInsetsController(); + if (insetsController != null) { + insetsController.hide(WindowInsets.Type.ime()); + } } @Nullable private View getViewToShowEducationTip() { @@ -850,15 +851,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet /** Shows education dialog for widgets. */ private WidgetsEduView showEducationDialog() { - mActivityContext.getSharedPrefs().edit() - .putBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, true).apply(); + LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_DIALOG_SEEN, true); return WidgetsEduView.showEducationDialog(mActivityContext); } /** Returns {@code true} if education dialog has previously been shown. */ protected boolean hasSeenEducationDialog() { - return mActivityContext.getSharedPrefs() - .getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false) + return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_DIALOG_SEEN) || Utilities.isRunningInTestHarness(); } diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index d85737b28f..c3ab08c153 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget.picker; +import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker; + import android.content.Context; import android.graphics.Outline; import android.os.Process; @@ -23,12 +25,15 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; import androidx.annotation.NonNull; +import androidx.annotation.Px; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.recyclerview.ViewHolderBinder; import com.android.launcher3.util.PackageUserKey; @@ -46,6 +51,8 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { private static final int PERSONAL_TAB = 0; private static final int WORK_TAB = 1; + private static final int MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268; + private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395; private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry"; private LinearLayout mSuggestedWidgetsContainer; @@ -117,6 +124,29 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (changed && mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) { + LinearLayout layout = mContent.findViewById(R.id.linear_layout_container); + FrameLayout leftPane = layout.findViewById(R.id.recycler_view_container); + LinearLayout.LayoutParams layoutParams = (LayoutParams) leftPane.getLayoutParams(); + // Width is 1/3 of the sheet unless it's less than min width or max width + int leftPaneWidth = layout.getMeasuredWidth() / 3; + @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); + @Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); + if (leftPaneWidth < minLeftPaneWidthPx) { + layoutParams.width = minLeftPaneWidthPx; + } else if (leftPaneWidth > maxLeftPaneWidthPx) { + layoutParams.width = maxLeftPaneWidthPx; + } else { + layoutParams.width = 0; + } + layoutParams.weight = layoutParams.width == 0 ? 0.33F : 0; + leftPane.setLayoutParams(layoutParams); + } + } + + @Override public void onRecommendedWidgetsBound() { super.onRecommendedWidgetsBound(); |