Framework changes for go/widget-size-specification
Update the framework to:
1 - Allow creating RemoteViews with a mapping from size to layouts
2 - Use the closes sized layout in any given situation
3 - Allow the launher to specify the current size when inflating a
remote views
Bug: 179025145
Test: atest android.widget.cts.RemoteViewsSizeMapTest
Change-Id: Icf98d01bd0cf8b48c47555a1af6acb498b46b1a4
This commit is contained in:
@@ -8294,6 +8294,7 @@ package android.appwidget {
|
||||
public class AppWidgetHostView extends android.widget.FrameLayout {
|
||||
ctor public AppWidgetHostView(android.content.Context);
|
||||
ctor public AppWidgetHostView(android.content.Context, int, int);
|
||||
method public void clearCurrentSize();
|
||||
method public int getAppWidgetId();
|
||||
method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo();
|
||||
method public static android.graphics.Rect getDefaultPaddingForWidget(android.content.Context, android.content.ComponentName, android.graphics.Rect);
|
||||
@@ -8301,11 +8302,13 @@ package android.appwidget {
|
||||
method protected android.view.View getErrorView();
|
||||
method protected void prepareView(android.view.View);
|
||||
method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo);
|
||||
method public void setCurrentSize(@NonNull android.graphics.PointF);
|
||||
method public void setExecutor(java.util.concurrent.Executor);
|
||||
method public void setOnLightBackground(boolean);
|
||||
method public void updateAppWidget(android.widget.RemoteViews);
|
||||
method public void updateAppWidgetOptions(android.os.Bundle);
|
||||
method public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
|
||||
method @Deprecated public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
|
||||
method public void updateAppWidgetSize(@NonNull android.os.Bundle, @NonNull java.util.List<android.graphics.PointF>);
|
||||
}
|
||||
|
||||
public class AppWidgetManager {
|
||||
@@ -8358,6 +8361,7 @@ package android.appwidget {
|
||||
field public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight";
|
||||
field public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
|
||||
field public static final String OPTION_APPWIDGET_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
|
||||
field public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes";
|
||||
}
|
||||
|
||||
public class AppWidgetProvider extends android.content.BroadcastReceiver {
|
||||
@@ -54618,6 +54622,7 @@ package android.widget {
|
||||
public class RemoteViews implements android.view.LayoutInflater.Filter android.os.Parcelable {
|
||||
ctor public RemoteViews(String, int);
|
||||
ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews);
|
||||
ctor public RemoteViews(@NonNull java.util.Map<android.graphics.PointF,android.widget.RemoteViews>);
|
||||
ctor public RemoteViews(android.widget.RemoteViews);
|
||||
ctor public RemoteViews(android.os.Parcel);
|
||||
method public void addView(@IdRes int, android.widget.RemoteViews);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.appwidget;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
@@ -29,6 +30,7 @@ import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -53,6 +55,7 @@ import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@@ -89,6 +92,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
int mLayoutId = -1;
|
||||
private OnClickHandler mOnClickHandler;
|
||||
private boolean mOnLightBackground;
|
||||
PointF mCurrentSize = null;
|
||||
|
||||
private Executor mAsyncExecutor;
|
||||
private CancellationSignal mLastExecutionSignal;
|
||||
@@ -268,7 +272,8 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
* Provide guidance about the size of this widget to the AppWidgetManager. The widths and
|
||||
* heights should correspond to the full area the AppWidgetHostView is given. Padding added by
|
||||
* the framework will be accounted for automatically. This information gets embedded into the
|
||||
* AppWidget options and causes a callback to the AppWidgetProvider.
|
||||
* AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of
|
||||
* sizes is explicitly set to an empty list.
|
||||
* @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
|
||||
*
|
||||
* @param newOptions The bundle of options, in addition to the size information,
|
||||
@@ -277,13 +282,96 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
* @param minHeight The maximum height in dips that the widget will be displayed at.
|
||||
* @param maxWidth The maximum width in dips that the widget will be displayed at.
|
||||
* @param maxHeight The maximum height in dips that the widget will be displayed at.
|
||||
*
|
||||
* @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
|
||||
int maxHeight) {
|
||||
updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide guidance about the size of this widget to the AppWidgetManager. The sizes should
|
||||
* correspond to the full area the AppWidgetHostView is given. Padding added by the framework
|
||||
* will be accounted for automatically.
|
||||
*
|
||||
* This method will update the option bundle with the list of sizes and the min/max bounds for
|
||||
* width and height.
|
||||
*
|
||||
* @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
|
||||
*
|
||||
* @param newOptions The bundle of options, in addition to the size information.
|
||||
* @param sizes Sizes, in dips, the widget may be displayed at without calling the provider
|
||||
* again. Typically, this will be size of the widget in landscape and portrait.
|
||||
* On some foldables, this might include the size on the outer and inner screens.
|
||||
*/
|
||||
public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<PointF> sizes) {
|
||||
AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
|
||||
|
||||
Rect padding = getDefaultPadding();
|
||||
float density = getResources().getDisplayMetrics().density;
|
||||
|
||||
float xPaddingDips = (padding.left + padding.right) / density;
|
||||
float yPaddingDips = (padding.top + padding.bottom) / density;
|
||||
|
||||
ArrayList<PointF> paddedSizes = new ArrayList<>(sizes.size());
|
||||
float minWidth = Float.MAX_VALUE;
|
||||
float maxWidth = 0;
|
||||
float minHeight = Float.MAX_VALUE;
|
||||
float maxHeight = 0;
|
||||
for (int i = 0; i < sizes.size(); i++) {
|
||||
PointF size = sizes.get(i);
|
||||
PointF paddedPoint = new PointF(Math.max(0.f, size.x - xPaddingDips),
|
||||
Math.max(0.f, size.y - yPaddingDips));
|
||||
paddedSizes.add(paddedPoint);
|
||||
minWidth = Math.min(minWidth, paddedPoint.x);
|
||||
maxWidth = Math.max(maxWidth, paddedPoint.x);
|
||||
minHeight = Math.min(minHeight, paddedPoint.y);
|
||||
maxHeight = Math.max(maxHeight, paddedPoint.y);
|
||||
}
|
||||
if (paddedSizes.equals(
|
||||
widgetManager.getAppWidgetOptions(mAppWidgetId).<PointF>getParcelableArrayList(
|
||||
AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
|
||||
return;
|
||||
}
|
||||
Bundle options = newOptions.deepCopy();
|
||||
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth);
|
||||
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight);
|
||||
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth);
|
||||
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight);
|
||||
options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
|
||||
updateAppWidgetOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current size of the widget. This should be the full area the AppWidgetHostView is
|
||||
* given. Padding added by the framework will be accounted for automatically.
|
||||
*
|
||||
* This size will be used to choose the appropriate layout the next time the {@link RemoteViews}
|
||||
* is re-inflated, if it was created with {@link RemoteViews#RemoteViews(Map)} .
|
||||
*/
|
||||
public void setCurrentSize(@NonNull PointF size) {
|
||||
Rect padding = getDefaultPadding();
|
||||
float density = getResources().getDisplayMetrics().density;
|
||||
float xPaddingDips = (padding.left + padding.right) / density;
|
||||
float yPaddingDips = (padding.top + padding.bottom) / density;
|
||||
PointF newSize = new PointF(size.x - xPaddingDips, size.y - yPaddingDips);
|
||||
if (!newSize.equals(mCurrentSize)) {
|
||||
mCurrentSize = newSize;
|
||||
mLayoutId = -1; // Prevents recycling the view.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current size, indicating it is not currently known.
|
||||
*/
|
||||
public void clearCurrentSize() {
|
||||
if (mCurrentSize != null) {
|
||||
mCurrentSize = null;
|
||||
mLayoutId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -322,6 +410,8 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
|
||||
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
|
||||
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
|
||||
newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
|
||||
new ArrayList<PointF>());
|
||||
updateAppWidgetOptions(newOptions);
|
||||
}
|
||||
}
|
||||
@@ -440,7 +530,7 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
// Try normal RemoteView inflation
|
||||
if (content == null) {
|
||||
try {
|
||||
content = remoteViews.apply(mContext, this, mOnClickHandler);
|
||||
content = remoteViews.apply(mContext, this, mOnClickHandler, mCurrentSize);
|
||||
if (LOGD) Log.d(TAG, "had to inflate new layout");
|
||||
} catch (RuntimeException e) {
|
||||
exception = e;
|
||||
@@ -492,7 +582,8 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
mView,
|
||||
mAsyncExecutor,
|
||||
new ViewApplyListener(remoteViews, layoutId, true),
|
||||
mOnClickHandler);
|
||||
mOnClickHandler,
|
||||
mCurrentSize);
|
||||
} catch (Exception e) {
|
||||
// Reapply failed. Try apply
|
||||
}
|
||||
@@ -502,7 +593,8 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
this,
|
||||
mAsyncExecutor,
|
||||
new ViewApplyListener(remoteViews, layoutId, false),
|
||||
mOnClickHandler);
|
||||
mOnClickHandler,
|
||||
mCurrentSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +625,8 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
AppWidgetHostView.this,
|
||||
mAsyncExecutor,
|
||||
new ViewApplyListener(mViews, mLayoutId, false),
|
||||
mOnClickHandler);
|
||||
mOnClickHandler,
|
||||
mCurrentSize);
|
||||
} else {
|
||||
applyContent(null, false, e);
|
||||
}
|
||||
|
||||
@@ -216,6 +216,12 @@ public class AppWidgetManager {
|
||||
*/
|
||||
public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight";
|
||||
|
||||
/**
|
||||
* A bundle extra ({@code List<PointF>}) that contains the list of possible sizes, in dips, a
|
||||
* widget instance can take.
|
||||
*/
|
||||
public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes";
|
||||
|
||||
/**
|
||||
* A bundle extra that hints to the AppWidgetProvider the category of host that owns this
|
||||
* this widget. Can have the value {@link
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -99,6 +100,8 @@ import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Stack;
|
||||
@@ -171,6 +174,11 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
*/
|
||||
private static final int MAX_NESTED_VIEWS = 10;
|
||||
|
||||
/**
|
||||
* Maximum number of RemoteViews that can be specified in constructor.
|
||||
*/
|
||||
private static final int MAX_INIT_VIEW_COUNT = 16;
|
||||
|
||||
// The unique identifiers for each custom {@link Action}.
|
||||
private static final int SET_ON_CLICK_RESPONSE_TAG = 1;
|
||||
private static final int REFLECTION_ACTION_TAG = 2;
|
||||
@@ -290,7 +298,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
* The resource ID of the layout file. (Added to the parcel)
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
private final int mLayoutId;
|
||||
private int mLayoutId;
|
||||
|
||||
/**
|
||||
* The resource ID of the layout file in dark text mode. (Added to the parcel)
|
||||
@@ -322,6 +330,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
*/
|
||||
private static final int MODE_NORMAL = 0;
|
||||
private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
|
||||
private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2;
|
||||
|
||||
/**
|
||||
* Used in conjunction with the special constructor
|
||||
@@ -331,12 +340,26 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
private RemoteViews mLandscape = null;
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
private RemoteViews mPortrait = null;
|
||||
/**
|
||||
* List of RemoteViews with their ideal size. There must be at least two if the map is not null.
|
||||
*
|
||||
* The smallest remote view is always the last element in the list.
|
||||
*/
|
||||
private List<RemoteViews> mSizedRemoteViews = null;
|
||||
|
||||
/**
|
||||
* Ideal size for this RemoteViews.
|
||||
*
|
||||
* Only to be used on children views used in a {@link RemoteViews} with
|
||||
* {@link RemoteViews#hasSizedRemoteViews()}.
|
||||
*/
|
||||
private PointF mIdealSize = null;
|
||||
|
||||
@ApplyFlags
|
||||
private int mApplyFlags = 0;
|
||||
|
||||
/** Class cookies of the Parcel this instance was read from. */
|
||||
private final Map<Class, Object> mClassCookies;
|
||||
private Map<Class, Object> mClassCookies;
|
||||
|
||||
private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = (view, pendingIntent, response)
|
||||
-> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
|
||||
@@ -2768,23 +2791,50 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
mClassCookies = null;
|
||||
}
|
||||
|
||||
private boolean hasMultipleLayouts() {
|
||||
return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews();
|
||||
}
|
||||
|
||||
private boolean hasLandscapeAndPortraitLayouts() {
|
||||
return (mLandscape != null) && (mPortrait != null);
|
||||
}
|
||||
|
||||
private boolean hasSizedRemoteViews() {
|
||||
return mSizedRemoteViews != null;
|
||||
}
|
||||
|
||||
private @Nullable PointF getIdealSize() {
|
||||
return mIdealSize;
|
||||
}
|
||||
|
||||
private void setIdealSize(@Nullable PointF size) {
|
||||
mIdealSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the smallest view in {@code mSizedRemoteViews}.
|
||||
* This method must not be called if {@code mSizedRemoteViews} is null.
|
||||
*/
|
||||
private RemoteViews findSmallestRemoteView() {
|
||||
return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RemoteViews object that will inflate as the specified
|
||||
* landspace or portrait RemoteViews, depending on the current configuration.
|
||||
*
|
||||
* @param landscape The RemoteViews to inflate in landscape configuration
|
||||
* @param portrait The RemoteViews to inflate in portrait configuration
|
||||
* @throws IllegalArgumentException if either landscape or portrait are null or if they are
|
||||
* not from the same application
|
||||
*/
|
||||
public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
|
||||
if (landscape == null || portrait == null) {
|
||||
throw new RuntimeException("Both RemoteViews must be non-null");
|
||||
throw new IllegalArgumentException("Both RemoteViews must be non-null");
|
||||
}
|
||||
if (!landscape.hasSameAppInfo(portrait.mApplication)) {
|
||||
throw new RuntimeException("Both RemoteViews must share the same package and user");
|
||||
throw new IllegalArgumentException(
|
||||
"Both RemoteViews must share the same package and user");
|
||||
}
|
||||
mApplication = portrait.mApplication;
|
||||
mLayoutId = portrait.mLayoutId;
|
||||
@@ -2801,10 +2851,85 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
? portrait.mClassCookies : landscape.mClassCookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RemoteViews object that will inflate the layout with the closest size
|
||||
* specification.
|
||||
*
|
||||
* The default remote views in that case is always the smallest one provided.
|
||||
*
|
||||
* @param remoteViews Mapping of size to layout.
|
||||
* @throws IllegalArgumentException if the map is empty, there are more than
|
||||
* MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application.
|
||||
*/
|
||||
public RemoteViews(@NonNull Map<PointF, RemoteViews> remoteViews) {
|
||||
if (remoteViews.isEmpty()) {
|
||||
throw new IllegalArgumentException("The set of RemoteViews cannot be empty");
|
||||
}
|
||||
if (remoteViews.size() > MAX_INIT_VIEW_COUNT) {
|
||||
throw new IllegalArgumentException("Too many RemoteViews in constructor");
|
||||
}
|
||||
if (remoteViews.size() == 1) {
|
||||
initializeFrom(remoteViews.values().iterator().next());
|
||||
return;
|
||||
}
|
||||
mBitmapCache = new BitmapCache();
|
||||
mClassCookies = initializeSizedRemoteViews(
|
||||
remoteViews.entrySet().stream().map(
|
||||
entry -> {
|
||||
entry.getValue().setIdealSize(entry.getKey());
|
||||
return entry.getValue();
|
||||
}
|
||||
).iterator()
|
||||
);
|
||||
|
||||
RemoteViews smallestView = findSmallestRemoteView();
|
||||
mApplication = smallestView.mApplication;
|
||||
mLayoutId = smallestView.mLayoutId;
|
||||
mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
|
||||
}
|
||||
|
||||
// Initialize mSizedRemoteViews and return the class cookies.
|
||||
private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) {
|
||||
List<RemoteViews> sizedRemoteViews = new ArrayList<>();
|
||||
Map<Class, Object> classCookies = null;
|
||||
float viewArea = Float.MAX_VALUE;
|
||||
RemoteViews smallestView = null;
|
||||
while (remoteViews.hasNext()) {
|
||||
RemoteViews view = remoteViews.next();
|
||||
PointF size = view.getIdealSize();
|
||||
float newViewArea = size.x * size.y;
|
||||
if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) {
|
||||
throw new IllegalArgumentException(
|
||||
"All RemoteViews must share the same package and user");
|
||||
}
|
||||
if (smallestView == null || newViewArea < viewArea) {
|
||||
if (smallestView != null) {
|
||||
sizedRemoteViews.add(smallestView);
|
||||
}
|
||||
viewArea = newViewArea;
|
||||
smallestView = view;
|
||||
} else {
|
||||
sizedRemoteViews.add(view);
|
||||
}
|
||||
configureRemoteViewsAsChild(view);
|
||||
view.setIdealSize(size);
|
||||
if (classCookies == null) {
|
||||
classCookies = view.mClassCookies;
|
||||
}
|
||||
}
|
||||
sizedRemoteViews.add(smallestView);
|
||||
mSizedRemoteViews = sizedRemoteViews;
|
||||
return classCookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of another RemoteViews.
|
||||
*/
|
||||
public RemoteViews(RemoteViews src) {
|
||||
initializeFrom(src);
|
||||
}
|
||||
|
||||
private void initializeFrom(RemoteViews src) {
|
||||
mBitmapCache = src.mBitmapCache;
|
||||
mApplication = src.mApplication;
|
||||
mIsRoot = src.mIsRoot;
|
||||
@@ -2812,12 +2937,20 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
|
||||
mApplyFlags = src.mApplyFlags;
|
||||
mClassCookies = src.mClassCookies;
|
||||
mIdealSize = src.mIdealSize;
|
||||
|
||||
if (src.hasLandscapeAndPortraitLayouts()) {
|
||||
mLandscape = new RemoteViews(src.mLandscape);
|
||||
mPortrait = new RemoteViews(src.mPortrait);
|
||||
}
|
||||
|
||||
if (src.hasSizedRemoteViews()) {
|
||||
mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size());
|
||||
for (RemoteViews srcView : src.mSizedRemoteViews) {
|
||||
mSizedRemoteViews.add(new RemoteViews(srcView));
|
||||
}
|
||||
}
|
||||
|
||||
if (src.mActions != null) {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.putClassCookies(mClassCookies);
|
||||
@@ -2867,10 +3000,29 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
if (mode == MODE_NORMAL) {
|
||||
mApplication = parcel.readInt() == 0 ? info :
|
||||
ApplicationInfo.CREATOR.createFromParcel(parcel);
|
||||
mIdealSize = parcel.readInt() == 0 ? null : PointF.CREATOR.createFromParcel(parcel);
|
||||
mLayoutId = parcel.readInt();
|
||||
mLightBackgroundLayoutId = parcel.readInt();
|
||||
|
||||
readActionsFromParcel(parcel, depth);
|
||||
} else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) {
|
||||
int numViews = parcel.readInt();
|
||||
if (numViews > MAX_INIT_VIEW_COUNT) {
|
||||
throw new IllegalArgumentException(
|
||||
"Too many views in mapping from size to RemoteViews.");
|
||||
}
|
||||
List<RemoteViews> remoteViews = new ArrayList<>(numViews);
|
||||
for (int i = 0; i < numViews; i++) {
|
||||
RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth,
|
||||
mClassCookies);
|
||||
info = view.mApplication;
|
||||
remoteViews.add(view);
|
||||
}
|
||||
initializeSizedRemoteViews(remoteViews.iterator());
|
||||
RemoteViews smallestView = findSmallestRemoteView();
|
||||
mApplication = smallestView.mApplication;
|
||||
mLayoutId = smallestView.mLayoutId;
|
||||
mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
|
||||
} else {
|
||||
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
|
||||
mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
|
||||
@@ -2990,16 +3142,20 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
*/
|
||||
private void setBitmapCache(BitmapCache bitmapCache) {
|
||||
mBitmapCache = bitmapCache;
|
||||
if (!hasLandscapeAndPortraitLayouts()) {
|
||||
if (hasSizedRemoteViews()) {
|
||||
for (RemoteViews remoteView : mSizedRemoteViews) {
|
||||
remoteView.setBitmapCache(bitmapCache);
|
||||
}
|
||||
} else if (hasLandscapeAndPortraitLayouts()) {
|
||||
mLandscape.setBitmapCache(bitmapCache);
|
||||
mPortrait.setBitmapCache(bitmapCache);
|
||||
} else {
|
||||
if (mActions != null) {
|
||||
final int count = mActions.size();
|
||||
for (int i= 0; i < count; ++i) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
mActions.get(i).setBitmapCache(bitmapCache);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mLandscape.setBitmapCache(bitmapCache);
|
||||
mPortrait.setBitmapCache(bitmapCache);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3018,10 +3174,10 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
* @param a The action to add
|
||||
*/
|
||||
private void addAction(Action a) {
|
||||
if (hasLandscapeAndPortraitLayouts()) {
|
||||
throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
|
||||
" layouts cannot be modified. Instead, fully configure the landscape and" +
|
||||
" portrait layouts individually before constructing the combined layout.");
|
||||
if (hasMultipleLayouts()) {
|
||||
throw new RuntimeException("RemoteViews specifying separate layouts for orientation"
|
||||
+ " or size cannot be modified. Instead, fully configure each layouts"
|
||||
+ " individually before constructing the combined layout.");
|
||||
}
|
||||
if (mActions == null) {
|
||||
mActions = new ArrayList<>();
|
||||
@@ -4100,13 +4256,78 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
int orientation = context.getResources().getConfiguration().orientation;
|
||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
return mLandscape;
|
||||
} else {
|
||||
return mPortrait;
|
||||
}
|
||||
return mPortrait;
|
||||
}
|
||||
if (hasSizedRemoteViews()) {
|
||||
return findSmallestRemoteView();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square distance between two points.
|
||||
*
|
||||
* This is particularly useful when we only care about the ordering of the distances.
|
||||
*/
|
||||
private static float squareDistance(PointF p1, PointF p2) {
|
||||
float dx = p1.x - p2.x;
|
||||
float dy = p1.y - p2.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the layout fits in the space available to the widget.
|
||||
*
|
||||
* A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions
|
||||
* are smaller than the ones of the widget, adding some padding to account for rounding errors.
|
||||
*/
|
||||
private static boolean fitsIn(PointF sizeLayout, @Nullable PointF sizeWidget) {
|
||||
return sizeWidget != null && (Math.ceil(sizeWidget.x) + 1 > sizeLayout.x)
|
||||
&& (Math.ceil(sizeWidget.y) + 1 > sizeLayout.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most appropriate {@link RemoteViews} given the context and, if not null, the
|
||||
* size of the widget.
|
||||
*
|
||||
* If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is
|
||||
* the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the
|
||||
* diagonal the most similar to the widget. If no layout fits or the size of the widget is
|
||||
* not specified, the one with the smallest area will be chosen.
|
||||
*/
|
||||
private RemoteViews getRemoteViewsToApply(@NonNull Context context,
|
||||
@Nullable PointF widgetSize) {
|
||||
if (!hasSizedRemoteViews()) {
|
||||
// If there isn't multiple remote views, fall back on the previous methods.
|
||||
return getRemoteViewsToApply(context);
|
||||
}
|
||||
// Find the better remote view
|
||||
RemoteViews bestFit = null;
|
||||
float bestSqDist = Float.MAX_VALUE;
|
||||
for (RemoteViews layout : mSizedRemoteViews) {
|
||||
PointF layoutSize = layout.getIdealSize();
|
||||
if (fitsIn(layoutSize, widgetSize)) {
|
||||
if (bestFit == null) {
|
||||
bestFit = layout;
|
||||
bestSqDist = squareDistance(layoutSize, widgetSize);
|
||||
} else {
|
||||
float newSqDist = squareDistance(layoutSize, widgetSize);
|
||||
if (newSqDist < bestSqDist) {
|
||||
bestFit = layout;
|
||||
bestSqDist = newSqDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestFit == null) {
|
||||
Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize);
|
||||
return findSmallestRemoteView();
|
||||
}
|
||||
return bestFit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inflates the view hierarchy represented by this object and applies
|
||||
* all of the actions.
|
||||
@@ -4124,7 +4345,13 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
|
||||
/** @hide */
|
||||
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context);
|
||||
return apply(context, parent, handler, null);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public View apply(@NonNull Context context, @NonNull ViewGroup parent,
|
||||
@Nullable OnClickHandler handler, @Nullable PointF size) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
|
||||
|
||||
View result = inflateView(context, rvToApply, parent);
|
||||
rvToApply.performApply(result, parent, handler);
|
||||
@@ -4132,9 +4359,17 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler,
|
||||
public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
|
||||
@Nullable OnClickHandler handler,
|
||||
@StyleRes int applyThemeResId) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context);
|
||||
return applyWithTheme(context, parent, handler, applyThemeResId, null);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
|
||||
@Nullable OnClickHandler handler,
|
||||
@StyleRes int applyThemeResId, @Nullable PointF size) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
|
||||
|
||||
View result = inflateView(context, rvToApply, parent, applyThemeResId);
|
||||
rvToApply.performApply(result, parent, handler);
|
||||
@@ -4219,12 +4454,26 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
/** @hide */
|
||||
public CancellationSignal applyAsync(Context context, ViewGroup parent,
|
||||
Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
|
||||
return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
|
||||
return applyAsync(context, parent, executor, listener, handler, null);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public CancellationSignal applyAsync(Context context, ViewGroup parent,
|
||||
Executor executor, OnViewAppliedListener listener, OnClickHandler handler,
|
||||
PointF size) {
|
||||
return getAsyncApplyTask(context, parent, listener, handler, size).startTaskOnExecutor(
|
||||
executor);
|
||||
}
|
||||
|
||||
private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
|
||||
OnViewAppliedListener listener, OnClickHandler handler) {
|
||||
return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
|
||||
return getAsyncApplyTask(context, parent, listener, handler, null);
|
||||
}
|
||||
|
||||
private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
|
||||
OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
|
||||
return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context,
|
||||
listener,
|
||||
handler, null);
|
||||
}
|
||||
|
||||
@@ -4341,12 +4590,18 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
|
||||
/** @hide */
|
||||
public void reapply(Context context, View v, OnClickHandler handler) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context);
|
||||
reapply(context, v, handler, null);
|
||||
}
|
||||
|
||||
// In the case that a view has this RemoteViews applied in one orientation, is persisted
|
||||
// across orientation change, and has the RemoteViews re-applied in the new orientation,
|
||||
// we throw an exception, since the layouts may be completely unrelated.
|
||||
if (hasLandscapeAndPortraitLayouts()) {
|
||||
/** @hide */
|
||||
public void reapply(Context context, View v, OnClickHandler handler, PointF size) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
|
||||
|
||||
// In the case that a view has this RemoteViews applied in one orientation or size, is
|
||||
// persisted across change, and has the RemoteViews re-applied in a different situation
|
||||
// (orientation or size), we throw an exception, since the layouts may be completely
|
||||
// unrelated.
|
||||
if (hasMultipleLayouts()) {
|
||||
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
|
||||
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
|
||||
" that does not share the same root layout id.");
|
||||
@@ -4377,12 +4632,18 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
/** @hide */
|
||||
public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
|
||||
OnViewAppliedListener listener, OnClickHandler handler) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context);
|
||||
return reapplyAsync(context, v, executor, listener, handler, null);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
|
||||
OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
|
||||
|
||||
// In the case that a view has this RemoteViews applied in one orientation, is persisted
|
||||
// across orientation change, and has the RemoteViews re-applied in the new orientation,
|
||||
// we throw an exception, since the layouts may be completely unrelated.
|
||||
if (hasLandscapeAndPortraitLayouts()) {
|
||||
if (hasMultipleLayouts()) {
|
||||
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
|
||||
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
|
||||
" that does not share the same root layout id.");
|
||||
@@ -4466,7 +4727,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
if (!hasLandscapeAndPortraitLayouts()) {
|
||||
if (!hasMultipleLayouts()) {
|
||||
dest.writeInt(MODE_NORMAL);
|
||||
// We only write the bitmap cache if we are the root RemoteViews, as this cache
|
||||
// is shared by all children.
|
||||
@@ -4479,9 +4740,26 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
dest.writeInt(1);
|
||||
mApplication.writeToParcel(dest, flags);
|
||||
}
|
||||
if (mIsRoot || mIdealSize == null) {
|
||||
dest.writeInt(0);
|
||||
} else {
|
||||
dest.writeInt(1);
|
||||
mIdealSize.writeToParcel(dest, flags);
|
||||
}
|
||||
dest.writeInt(mLayoutId);
|
||||
dest.writeInt(mLightBackgroundLayoutId);
|
||||
writeActionsToParcel(dest);
|
||||
} else if (hasSizedRemoteViews()) {
|
||||
dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
|
||||
if (mIsRoot) {
|
||||
mBitmapCache.writeBitmapsToParcel(dest, flags);
|
||||
}
|
||||
int childFlags = flags;
|
||||
dest.writeInt(mSizedRemoteViews.size());
|
||||
for (RemoteViews view : mSizedRemoteViews) {
|
||||
view.writeToParcel(dest, childFlags);
|
||||
childFlags |= PARCELABLE_ELIDE_DUPLICATES;
|
||||
}
|
||||
} else {
|
||||
dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
|
||||
// We only write the bitmap cache if we are the root RemoteViews, as this cache
|
||||
@@ -4735,11 +5013,9 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
* before starting the intent.
|
||||
*
|
||||
* @param fillIntent The intent which will be combined with the parent's PendingIntent in
|
||||
* order to determine the behavior of the response
|
||||
*
|
||||
* order to determine the behavior of the response
|
||||
* @see RemoteViews#setPendingIntentTemplate(int, PendingIntent)
|
||||
* @see RemoteViews#setOnClickFillInIntent(int, Intent)
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
|
||||
@@ -4754,9 +5030,8 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
* the epicenter for the exit Transition. The position of the associated shared element in
|
||||
* the launched Activity will be the epicenter of its entering Transition.
|
||||
*
|
||||
* @param viewId The id of the view to be shared as part of the transition
|
||||
* @param viewId The id of the view to be shared as part of the transition
|
||||
* @param sharedElementName The shared element name for this view
|
||||
*
|
||||
* @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
|
||||
*/
|
||||
@NonNull
|
||||
|
||||
Reference in New Issue
Block a user