Loading core/java/android/appwidget/AppWidgetConfigActivityProxy.java 0 → 100644 +87 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 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 android.appwidget; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerWrapper; import androidx.annotation.Nullable; /** * Activity to proxy config activity launches * * @hide */ public class AppWidgetConfigActivityProxy extends Activity { private static final int CONFIG_ACTIVITY_REQUEST_CODE = 1; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setResult(RESULT_CANCELED); Intent intent = getIntent(); Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); if (target == null) { finish(); return; } startActivityForResult(target, CONFIG_ACTIVITY_REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { setResult(resultCode, data); int widgetId = getIntent().getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); AppWidgetManager.getInstance(this).setConfigActivityComplete(widgetId); finish(); } @Override public WindowManager getWindowManager() { return new MyWM(super.getWindowManager()); } /** Wrapper over windowManager with disables adding a window */ private static class MyWM extends WindowManagerWrapper { MyWM(WindowManager original) { super(original); } @Override public void addView(View view, ViewGroup.LayoutParams params) { } @Override public void updateViewLayout(View view, ViewGroup.LayoutParams params) { } @Override public void removeView(View view) { } @Override public void removeViewImmediate(View view) { } } } core/java/android/appwidget/AppWidgetManager.java +159 −55 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.TestApi; import android.annotation.UiThread; import android.annotation.UiThread; import android.annotation.UserIdInt; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.IServiceConnection; import android.app.IServiceConnection; import android.app.PendingIntent; import android.app.PendingIntent; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManager; Loading @@ -47,8 +48,6 @@ import android.os.Binder; import android.os.Build; import android.os.Build; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.IBinder; import android.os.Looper; import android.os.Looper; import android.os.Process; import android.os.Process; Loading @@ -57,7 +56,6 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.DisplayMetrics; import android.util.Log; import android.util.Log; import android.util.Pair; import android.widget.RemoteViews; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.appwidget.IAppWidgetService; Loading @@ -65,13 +63,14 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.FunctionalUtils; import com.android.internal.util.FunctionalUtils; import java.util.ArrayDeque; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.List; import java.util.List; import java.util.Map; import java.util.Map; import java.util.Objects; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.function.Consumer; /** /** Loading Loading @@ -583,6 +582,13 @@ public class AppWidgetManager { */ */ public static final String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview"; public static final String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview"; /** * The maximum waiting time for remote adapter conversion in milliseconds * * @hide */ private static final long MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 20_000; /** /** * Field for the manifest meta-data tag. * Field for the manifest meta-data tag. * * Loading @@ -600,7 +606,8 @@ public class AppWidgetManager { private boolean mHasPostedLegacyLists = false; private boolean mHasPostedLegacyLists = false; private @NonNull ServiceCollectionCache mServiceCollectionCache; @NonNull private final ServiceCollectionCache mServiceCollectionCache; /** /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context Loading Loading @@ -653,25 +660,40 @@ public class AppWidgetManager { private void tryAdapterConversion( private void tryAdapterConversion( FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, RemoteViews original, String failureMsg) { RemoteViews original, int[] appWidgetIds, String failureMsg) { if (remoteAdapterConversion() if (remoteAdapterConversion()) { && (mHasPostedLegacyLists = mHasPostedLegacyLists mHasPostedLegacyLists = mHasPostedLegacyLists || (original != null && original.hasLegacyLists()))) { || (original != null && original.hasLegacyLists()); } if (remoteAdapterConversion() && mHasPostedLegacyLists && original != null) { final RemoteViews viewsCopy = new RemoteViews(original); final RemoteViews viewsCopy = new RemoteViews(original); Runnable updateWidgetWithTask = () -> { Runnable updateWidgetWithTask = () -> { try { try { viewsCopy.collectAllIntents(mMaxBitmapMemory, mServiceCollectionCache).get(); if (shouldSkipListConversion(viewsCopy, appWidgetIds)) { Log.d(TAG, "Skipping legacy list conversion, pending config activity"); viewsCopy.replaceAllIntentsWithEmptyList(); } else { viewsCopy.collectAllIntents(mMaxBitmapMemory, false /* invalidateData */, mServiceCollectionCache) .thenRun(() -> { try { action.acceptOrThrow(viewsCopy); action.acceptOrThrow(viewsCopy); } catch (RemoteException e) { e.rethrowFromSystemServer(); } }).exceptionally(e -> { Log.e(TAG, failureMsg, e); return null; }); } } catch (Exception e) { } catch (Exception e) { Log.e(TAG, failureMsg, e); Log.e(TAG, failureMsg, e); } } }; }; if (Looper.getMainLooper() == Looper.myLooper()) { if (Looper.getMainLooper() == Looper.myLooper()) { createUpdateExecutorIfNull().execute(updateWidgetWithTask); createUpdateExecutorIfNull().execute(updateWidgetWithTask); return; return; } } updateWidgetWithTask.run(); updateWidgetWithTask.run(); } else { } else { try { try { Loading @@ -682,6 +704,13 @@ public class AppWidgetManager { } } } } private boolean shouldSkipListConversion(RemoteViews views, int[] appWidgetIds) throws RemoteException { return appWidgetIds.length == 1 && views.hasLegacyLists() && mService.isFirstConfigActivityPending(mPackageName, appWidgetIds[0]); } /** /** * Set the RemoteViews to use for the specified appWidgetIds. * Set the RemoteViews to use for the specified appWidgetIds. * <p> * <p> Loading @@ -707,7 +736,7 @@ public class AppWidgetManager { } } tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds, tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds, view), views, "Error updating app widget views in background"); view), views, appWidgetIds, "Error updating app widget views in background"); } } /** /** Loading Loading @@ -813,7 +842,7 @@ public class AppWidgetManager { } } tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName, tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, view), views, appWidgetIds, view), views, appWidgetIds, "Error partially updating app widget views in background"); "Error partially updating app widget views in background"); } } Loading Loading @@ -867,7 +896,7 @@ public class AppWidgetManager { } } tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views, tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views, "Error updating app widget view using provider in background"); new int[0], "Error updating app widget view using provider in background"); } } /** /** Loading Loading @@ -924,10 +953,10 @@ public class AppWidgetManager { } } if (remoteAdapterConversion()) { if (remoteAdapterConversion()) { if (Looper.myLooper() == Looper.getMainLooper()) { mHasPostedLegacyLists = true; mHasPostedLegacyLists = true; createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange( if (Looper.myLooper() == Looper.getMainLooper()) { appWidgetIds, viewId)); createUpdateExecutorIfNull().execute( () -> notifyCollectionWidgetChange(appWidgetIds, viewId)); } else { } else { notifyCollectionWidgetChange(appWidgetIds, viewId); notifyCollectionWidgetChange(appWidgetIds, viewId); } } Loading @@ -940,23 +969,36 @@ public class AppWidgetManager { } } } } @WorkerThread private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) { private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) { try { try { List<CompletableFuture<Void>> updateFutures = new ArrayList<>(); for (final int widgetId : appWidgetIds) { for (int i = 0; i < appWidgetIds.length; i++) { final int widgetId = appWidgetIds[i]; updateFutures.add(CompletableFuture.runAsync(() -> { try { try { if (mService.isFirstConfigActivityPending(mPackageName, widgetId)) { Log.d(TAG, "Skipping collection notify, pending config activity"); continue; } RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); if (views.replaceRemoteCollections(viewId)) { if (views == null || !views.replaceRemoteCollections(viewId)) { updateAppWidget(widgetId, views); continue; } } views.collectAllIntents( mMaxBitmapMemory, true /* invalidateData */, mServiceCollectionCache) .thenRun(() -> { try { mService.updateAppWidgetIds( mPackageName, new int[]{widgetId}, views); } catch (RemoteException e) { e.rethrowFromSystemServer(); } }).exceptionally(e -> { Log.e(TAG, "Error notifying changes in RemoteViews", e); return null; }); } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error notifying changes in RemoteViews", e); Log.e(TAG, "Error notifying changes in RemoteViews", e); } } })); } } CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join(); } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error notifying changes for all widgets", e); Log.e(TAG, "Error notifying changes for all widgets", e); } } Loading Loading @@ -1591,20 +1633,29 @@ public class AppWidgetManager { } } } } /** @hide */ public void setConfigActivityComplete(int widgetId) { try { mService.setConfigActivityComplete(widgetId); } catch (RemoteException e) { Log.d(TAG, "Error notifying config activity completed"); } } @UiThread @UiThread private static @NonNull Executor createUpdateExecutorIfNull() { private static @NonNull Executor createUpdateExecutorIfNull() { if (sUpdateExecutor == null) { if (sUpdateExecutor == null) { sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler( sUpdateExecutor = createExecutorService( "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND)); "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND); } } return sUpdateExecutor; return sUpdateExecutor; } } private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) { private static ExecutorService createExecutorService(@NonNull String name, int priority) { HandlerThread thread = new HandlerThread(name, priority); return Executors.newSingleThreadExecutor(r -> new Thread(() -> { thread.start(); Process.setThreadPriority(Process.myTid(), priority); return thread.getThreadHandler(); r.run(); }, name)); } } /** /** Loading @@ -1629,27 +1680,31 @@ public class AppWidgetManager { * Connect to the service indicated by the {@code Intent}, and consume the binder on the * Connect to the service indicated by the {@code Intent}, and consume the binder on the * specified executor * specified executor */ */ public void connectAndConsume(Intent intent, Consumer<IBinder> task, Executor executor) { public void connectAndConsume(Intent intent, Consumer<IBinder> task) { mHandler.post(() -> connectAndConsumeInner(intent, task, executor)); mHandler.post(() -> connectAndConsumeInner(intent, task)); } } private void connectAndConsumeInner(Intent intent, Consumer<IBinder> task, private void connectAndConsumeInner(Intent intent, Consumer<IBinder> task) { Executor executor) { ConnectionTask activeConnection = mActiveConnections.computeIfAbsent( ConnectionTask activeConnection = mActiveConnections.computeIfAbsent( new FilterComparison(intent), ConnectionTask::new); new FilterComparison(intent), ConnectionTask::new); activeConnection.add(task, executor); activeConnection.add(task); } } private class ConnectionTask implements ServiceConnection { private class ConnectionTask implements ServiceConnection { private final Runnable mDestroyAfterTimeout = this::onDestroyTimeout; private final Runnable mDestroyAfterTimeout = this::onDestroyTimeout; private final ArrayDeque<Pair<Consumer<IBinder>, Executor>> mTaskQueue = private final Runnable mConnectionTimeout = this::onConnectionTimeout; new ArrayDeque<>(); private final ArrayDeque<Consumer<IBinder>> mTaskQueue = new ArrayDeque<>(); private final String mExecutorName; private boolean mOnDestroyTimeout = false; private boolean mOnDestroyTimeout = false; private IBinder mIBinder; private IBinder mIBinder; private ExecutorService mBinderCallExecutor; private Object mCurrentTaskToken; ConnectionTask(@NonNull FilterComparison filter) { ConnectionTask(@NonNull FilterComparison filter) { mExecutorName = "appwidget-connectiontask-" + filter.hashCode(); try { try { mContext.bindService(filter.getIntent(), mContext.bindService(filter.getIntent(), Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), Loading @@ -1658,12 +1713,12 @@ public class AppWidgetManager { } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error connecting to service in connection cache", e); Log.e(TAG, "Error connecting to service in connection cache", e); } } mHandler.postDelayed(mConnectionTimeout, MAX_ADAPTER_CONVERSION_WAITING_TIME_MS); } } @Override @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mIBinder = iBinder; onBinderReceived(iBinder); mHandler.post(this::handleNext); } } @Override @Override Loading @@ -1672,26 +1727,56 @@ public class AppWidgetManager { onServiceConnected(name, new Binder()); onServiceConnected(name, new Binder()); } } private void onConnectionTimeout() { onBinderReceived(new Binder()); } private void onBinderReceived(IBinder iBinder) { if (mIBinder != null) { return; } mIBinder = iBinder; mHandler.removeCallbacks(mConnectionTimeout); mHandler.post(this::handleNext); } @Override @Override public void onServiceDisconnected(ComponentName componentName) { } public void onServiceDisconnected(ComponentName componentName) { } void add(Consumer<IBinder> task, Executor executor) { void add(Consumer<IBinder> task) { mTaskQueue.add(Pair.create(task, executor)); mTaskQueue.add(task); if (mOnDestroyTimeout) { if (mOnDestroyTimeout) { // If we are waiting for timeout, cancel it and execute the next task // If we are waiting for timeout, cancel it and execute the next task handleNext(); handleNext(); } } } } private void onTaskComplete(Object taskToken) { if (mCurrentTaskToken == taskToken) { mCurrentTaskToken = null; handleNext(); } } private void handleNext() { private void handleNext() { mHandler.removeCallbacks(mDestroyAfterTimeout); mHandler.removeCallbacks(mDestroyAfterTimeout); Pair<Consumer<IBinder>, Executor> next = mTaskQueue.pollFirst(); Consumer<IBinder> next = mTaskQueue.pollFirst(); if (next != null) { if (next != null) { mCurrentTaskToken = next; mOnDestroyTimeout = false; mOnDestroyTimeout = false; next.second.execute(() -> { if (mBinderCallExecutor == null) { next.first.accept(mIBinder); mBinderCallExecutor = createExecutorService( mHandler.post(this::handleNext); mExecutorName, Process.THREAD_PRIORITY_FOREGROUND); } Future task = mBinderCallExecutor.submit(() -> { next.accept(mIBinder); mHandler.post(() -> onTaskComplete(next)); }); }); mHandler.postDelayed( () -> onTaskTimeout(task, next), MAX_ADAPTER_CONVERSION_WAITING_TIME_MS); } else { } else { // Finished all tasks, start a timeout to unbind this service // Finished all tasks, start a timeout to unbind this service mOnDestroyTimeout = true; mOnDestroyTimeout = true; Loading @@ -1699,6 +1784,22 @@ public class AppWidgetManager { } } } } /** * If a task times out, we try to interrupt it, but also switch to a new executor, * in case the previous executor gets blocked for ever */ private void onTaskTimeout(Future task, Object taskToken) { if (!task.isDone()) { ExecutorService oldExecutor = mBinderCallExecutor; mBinderCallExecutor = null; task.cancel(true); if (oldExecutor != null) { oldExecutor.shutdown(); } onTaskComplete(taskToken); } } /** /** * Called after we have waited for {@link #mTimeOut} after the last task is finished * Called after we have waited for {@link #mTimeOut} after the last task is finished */ */ Loading @@ -1712,6 +1813,9 @@ public class AppWidgetManager { } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error unbinding the cached connection", e); Log.e(TAG, "Error unbinding the cached connection", e); } } if (mBinderCallExecutor != null) { mBinderCallExecutor.shutdown(); } mActiveConnections.values().remove(this); mActiveConnections.values().remove(this); } } } } Loading core/java/android/widget/RemoteViews.java +36 −40 File changed.Preview size limit exceeded, changes collapsed. Show changes core/java/android/widget/RemoteViewsService.java +6 −7 Original line number Original line Diff line number Diff line Loading @@ -130,8 +130,6 @@ public abstract class RemoteViewsService extends Service { */ */ default RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, default RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, int capBitmapSize) { int capBitmapSize) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); Parcel capSizeTestParcel = Parcel.obtain(); Parcel capSizeTestParcel = Parcel.obtain(); // restore allowSquashing to reduce the noise in error messages // restore allowSquashing to reduce the noise in error messages boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); Loading @@ -140,7 +138,6 @@ public abstract class RemoteViewsService extends Service { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = RemoteViews.RemoteCollectionItems.Builder itemsBuilder = new RemoteViews.RemoteCollectionItems.Builder(); new RemoteViews.RemoteCollectionItems.Builder(); RemoteViews.BitmapCache testBitmapCache = null; RemoteViews.BitmapCache testBitmapCache = null; onDataSetChanged(); itemsBuilder.setHasStableIds(hasStableIds()); itemsBuilder.setHasStableIds(hasStableIds()); final int numOfEntries = getCount(); final int numOfEntries = getCount(); Loading @@ -167,14 +164,12 @@ public abstract class RemoteViewsService extends Service { itemsBuilder.addItem(currentItemId, currentView); itemsBuilder.addItem(currentItemId, currentView); } } return itemsBuilder.build(); items = itemsBuilder.build(); } finally { } finally { capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); // Recycle the parcel // Recycle the parcel capSizeTestParcel.recycle(); capSizeTestParcel.recycle(); } } return items; } } } } Loading Loading @@ -282,10 +277,14 @@ public abstract class RemoteViewsService extends Service { @Override @Override public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, int capBitmapSize) { int capBitmapSize, boolean invalidateData) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); .Builder().build(); try { try { if (mIsCreated || invalidateData) { mFactory.onDataSetChanged(); mIsCreated = false; } items = mFactory.getRemoteCollectionItems(capSize, capBitmapSize); items = mFactory.getRemoteCollectionItems(capSize, capBitmapSize); } catch (Exception ex) { } catch (Exception ex) { Thread t = Thread.currentThread(); Thread t = Thread.currentThread(); Loading core/java/com/android/internal/appwidget/IAppWidgetService.aidl +4 −0 Original line number Original line Diff line number Diff line Loading @@ -89,5 +89,9 @@ interface IAppWidgetService { in ComponentName providerComponent, in int profileId, in int widgetCategory); in ComponentName providerComponent, in int profileId, in int widgetCategory); void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories); void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories); oneway void reportWidgetEvents(in String callingPackage, in AppWidgetEvent[] events); oneway void reportWidgetEvents(in String callingPackage, in AppWidgetEvent[] events); // For legacy list migration boolean isFirstConfigActivityPending(in String callingPackage, in int appWidgetId); oneway void setConfigActivityComplete(in int appWidgetId); } } Loading
core/java/android/appwidget/AppWidgetConfigActivityProxy.java 0 → 100644 +87 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 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 android.appwidget; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerWrapper; import androidx.annotation.Nullable; /** * Activity to proxy config activity launches * * @hide */ public class AppWidgetConfigActivityProxy extends Activity { private static final int CONFIG_ACTIVITY_REQUEST_CODE = 1; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setResult(RESULT_CANCELED); Intent intent = getIntent(); Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); if (target == null) { finish(); return; } startActivityForResult(target, CONFIG_ACTIVITY_REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { setResult(resultCode, data); int widgetId = getIntent().getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); AppWidgetManager.getInstance(this).setConfigActivityComplete(widgetId); finish(); } @Override public WindowManager getWindowManager() { return new MyWM(super.getWindowManager()); } /** Wrapper over windowManager with disables adding a window */ private static class MyWM extends WindowManagerWrapper { MyWM(WindowManager original) { super(original); } @Override public void addView(View view, ViewGroup.LayoutParams params) { } @Override public void updateViewLayout(View view, ViewGroup.LayoutParams params) { } @Override public void removeView(View view) { } @Override public void removeViewImmediate(View view) { } } }
core/java/android/appwidget/AppWidgetManager.java +159 −55 Original line number Original line Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.TestApi; import android.annotation.UiThread; import android.annotation.UiThread; import android.annotation.UserIdInt; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.IServiceConnection; import android.app.IServiceConnection; import android.app.PendingIntent; import android.app.PendingIntent; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManager; Loading @@ -47,8 +48,6 @@ import android.os.Binder; import android.os.Build; import android.os.Build; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.IBinder; import android.os.Looper; import android.os.Looper; import android.os.Process; import android.os.Process; Loading @@ -57,7 +56,6 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.DisplayMetrics; import android.util.Log; import android.util.Log; import android.util.Pair; import android.widget.RemoteViews; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.appwidget.IAppWidgetService; Loading @@ -65,13 +63,14 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.FunctionalUtils; import com.android.internal.util.FunctionalUtils; import java.util.ArrayDeque; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Collections; import java.util.List; import java.util.List; import java.util.Map; import java.util.Map; import java.util.Objects; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.function.Consumer; /** /** Loading Loading @@ -583,6 +582,13 @@ public class AppWidgetManager { */ */ public static final String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview"; public static final String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview"; /** * The maximum waiting time for remote adapter conversion in milliseconds * * @hide */ private static final long MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 20_000; /** /** * Field for the manifest meta-data tag. * Field for the manifest meta-data tag. * * Loading @@ -600,7 +606,8 @@ public class AppWidgetManager { private boolean mHasPostedLegacyLists = false; private boolean mHasPostedLegacyLists = false; private @NonNull ServiceCollectionCache mServiceCollectionCache; @NonNull private final ServiceCollectionCache mServiceCollectionCache; /** /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context Loading Loading @@ -653,25 +660,40 @@ public class AppWidgetManager { private void tryAdapterConversion( private void tryAdapterConversion( FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, RemoteViews original, String failureMsg) { RemoteViews original, int[] appWidgetIds, String failureMsg) { if (remoteAdapterConversion() if (remoteAdapterConversion()) { && (mHasPostedLegacyLists = mHasPostedLegacyLists mHasPostedLegacyLists = mHasPostedLegacyLists || (original != null && original.hasLegacyLists()))) { || (original != null && original.hasLegacyLists()); } if (remoteAdapterConversion() && mHasPostedLegacyLists && original != null) { final RemoteViews viewsCopy = new RemoteViews(original); final RemoteViews viewsCopy = new RemoteViews(original); Runnable updateWidgetWithTask = () -> { Runnable updateWidgetWithTask = () -> { try { try { viewsCopy.collectAllIntents(mMaxBitmapMemory, mServiceCollectionCache).get(); if (shouldSkipListConversion(viewsCopy, appWidgetIds)) { Log.d(TAG, "Skipping legacy list conversion, pending config activity"); viewsCopy.replaceAllIntentsWithEmptyList(); } else { viewsCopy.collectAllIntents(mMaxBitmapMemory, false /* invalidateData */, mServiceCollectionCache) .thenRun(() -> { try { action.acceptOrThrow(viewsCopy); action.acceptOrThrow(viewsCopy); } catch (RemoteException e) { e.rethrowFromSystemServer(); } }).exceptionally(e -> { Log.e(TAG, failureMsg, e); return null; }); } } catch (Exception e) { } catch (Exception e) { Log.e(TAG, failureMsg, e); Log.e(TAG, failureMsg, e); } } }; }; if (Looper.getMainLooper() == Looper.myLooper()) { if (Looper.getMainLooper() == Looper.myLooper()) { createUpdateExecutorIfNull().execute(updateWidgetWithTask); createUpdateExecutorIfNull().execute(updateWidgetWithTask); return; return; } } updateWidgetWithTask.run(); updateWidgetWithTask.run(); } else { } else { try { try { Loading @@ -682,6 +704,13 @@ public class AppWidgetManager { } } } } private boolean shouldSkipListConversion(RemoteViews views, int[] appWidgetIds) throws RemoteException { return appWidgetIds.length == 1 && views.hasLegacyLists() && mService.isFirstConfigActivityPending(mPackageName, appWidgetIds[0]); } /** /** * Set the RemoteViews to use for the specified appWidgetIds. * Set the RemoteViews to use for the specified appWidgetIds. * <p> * <p> Loading @@ -707,7 +736,7 @@ public class AppWidgetManager { } } tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds, tryAdapterConversion(view -> mService.updateAppWidgetIds(mPackageName, appWidgetIds, view), views, "Error updating app widget views in background"); view), views, appWidgetIds, "Error updating app widget views in background"); } } /** /** Loading Loading @@ -813,7 +842,7 @@ public class AppWidgetManager { } } tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName, tryAdapterConversion(view -> mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, view), views, appWidgetIds, view), views, appWidgetIds, "Error partially updating app widget views in background"); "Error partially updating app widget views in background"); } } Loading Loading @@ -867,7 +896,7 @@ public class AppWidgetManager { } } tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views, tryAdapterConversion(view -> mService.updateAppWidgetProvider(provider, view), views, "Error updating app widget view using provider in background"); new int[0], "Error updating app widget view using provider in background"); } } /** /** Loading Loading @@ -924,10 +953,10 @@ public class AppWidgetManager { } } if (remoteAdapterConversion()) { if (remoteAdapterConversion()) { if (Looper.myLooper() == Looper.getMainLooper()) { mHasPostedLegacyLists = true; mHasPostedLegacyLists = true; createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange( if (Looper.myLooper() == Looper.getMainLooper()) { appWidgetIds, viewId)); createUpdateExecutorIfNull().execute( () -> notifyCollectionWidgetChange(appWidgetIds, viewId)); } else { } else { notifyCollectionWidgetChange(appWidgetIds, viewId); notifyCollectionWidgetChange(appWidgetIds, viewId); } } Loading @@ -940,23 +969,36 @@ public class AppWidgetManager { } } } } @WorkerThread private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) { private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) { try { try { List<CompletableFuture<Void>> updateFutures = new ArrayList<>(); for (final int widgetId : appWidgetIds) { for (int i = 0; i < appWidgetIds.length; i++) { final int widgetId = appWidgetIds[i]; updateFutures.add(CompletableFuture.runAsync(() -> { try { try { if (mService.isFirstConfigActivityPending(mPackageName, widgetId)) { Log.d(TAG, "Skipping collection notify, pending config activity"); continue; } RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); if (views.replaceRemoteCollections(viewId)) { if (views == null || !views.replaceRemoteCollections(viewId)) { updateAppWidget(widgetId, views); continue; } } views.collectAllIntents( mMaxBitmapMemory, true /* invalidateData */, mServiceCollectionCache) .thenRun(() -> { try { mService.updateAppWidgetIds( mPackageName, new int[]{widgetId}, views); } catch (RemoteException e) { e.rethrowFromSystemServer(); } }).exceptionally(e -> { Log.e(TAG, "Error notifying changes in RemoteViews", e); return null; }); } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error notifying changes in RemoteViews", e); Log.e(TAG, "Error notifying changes in RemoteViews", e); } } })); } } CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join(); } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error notifying changes for all widgets", e); Log.e(TAG, "Error notifying changes for all widgets", e); } } Loading Loading @@ -1591,20 +1633,29 @@ public class AppWidgetManager { } } } } /** @hide */ public void setConfigActivityComplete(int widgetId) { try { mService.setConfigActivityComplete(widgetId); } catch (RemoteException e) { Log.d(TAG, "Error notifying config activity completed"); } } @UiThread @UiThread private static @NonNull Executor createUpdateExecutorIfNull() { private static @NonNull Executor createUpdateExecutorIfNull() { if (sUpdateExecutor == null) { if (sUpdateExecutor == null) { sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler( sUpdateExecutor = createExecutorService( "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND)); "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND); } } return sUpdateExecutor; return sUpdateExecutor; } } private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) { private static ExecutorService createExecutorService(@NonNull String name, int priority) { HandlerThread thread = new HandlerThread(name, priority); return Executors.newSingleThreadExecutor(r -> new Thread(() -> { thread.start(); Process.setThreadPriority(Process.myTid(), priority); return thread.getThreadHandler(); r.run(); }, name)); } } /** /** Loading @@ -1629,27 +1680,31 @@ public class AppWidgetManager { * Connect to the service indicated by the {@code Intent}, and consume the binder on the * Connect to the service indicated by the {@code Intent}, and consume the binder on the * specified executor * specified executor */ */ public void connectAndConsume(Intent intent, Consumer<IBinder> task, Executor executor) { public void connectAndConsume(Intent intent, Consumer<IBinder> task) { mHandler.post(() -> connectAndConsumeInner(intent, task, executor)); mHandler.post(() -> connectAndConsumeInner(intent, task)); } } private void connectAndConsumeInner(Intent intent, Consumer<IBinder> task, private void connectAndConsumeInner(Intent intent, Consumer<IBinder> task) { Executor executor) { ConnectionTask activeConnection = mActiveConnections.computeIfAbsent( ConnectionTask activeConnection = mActiveConnections.computeIfAbsent( new FilterComparison(intent), ConnectionTask::new); new FilterComparison(intent), ConnectionTask::new); activeConnection.add(task, executor); activeConnection.add(task); } } private class ConnectionTask implements ServiceConnection { private class ConnectionTask implements ServiceConnection { private final Runnable mDestroyAfterTimeout = this::onDestroyTimeout; private final Runnable mDestroyAfterTimeout = this::onDestroyTimeout; private final ArrayDeque<Pair<Consumer<IBinder>, Executor>> mTaskQueue = private final Runnable mConnectionTimeout = this::onConnectionTimeout; new ArrayDeque<>(); private final ArrayDeque<Consumer<IBinder>> mTaskQueue = new ArrayDeque<>(); private final String mExecutorName; private boolean mOnDestroyTimeout = false; private boolean mOnDestroyTimeout = false; private IBinder mIBinder; private IBinder mIBinder; private ExecutorService mBinderCallExecutor; private Object mCurrentTaskToken; ConnectionTask(@NonNull FilterComparison filter) { ConnectionTask(@NonNull FilterComparison filter) { mExecutorName = "appwidget-connectiontask-" + filter.hashCode(); try { try { mContext.bindService(filter.getIntent(), mContext.bindService(filter.getIntent(), Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE), Loading @@ -1658,12 +1713,12 @@ public class AppWidgetManager { } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error connecting to service in connection cache", e); Log.e(TAG, "Error connecting to service in connection cache", e); } } mHandler.postDelayed(mConnectionTimeout, MAX_ADAPTER_CONVERSION_WAITING_TIME_MS); } } @Override @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mIBinder = iBinder; onBinderReceived(iBinder); mHandler.post(this::handleNext); } } @Override @Override Loading @@ -1672,26 +1727,56 @@ public class AppWidgetManager { onServiceConnected(name, new Binder()); onServiceConnected(name, new Binder()); } } private void onConnectionTimeout() { onBinderReceived(new Binder()); } private void onBinderReceived(IBinder iBinder) { if (mIBinder != null) { return; } mIBinder = iBinder; mHandler.removeCallbacks(mConnectionTimeout); mHandler.post(this::handleNext); } @Override @Override public void onServiceDisconnected(ComponentName componentName) { } public void onServiceDisconnected(ComponentName componentName) { } void add(Consumer<IBinder> task, Executor executor) { void add(Consumer<IBinder> task) { mTaskQueue.add(Pair.create(task, executor)); mTaskQueue.add(task); if (mOnDestroyTimeout) { if (mOnDestroyTimeout) { // If we are waiting for timeout, cancel it and execute the next task // If we are waiting for timeout, cancel it and execute the next task handleNext(); handleNext(); } } } } private void onTaskComplete(Object taskToken) { if (mCurrentTaskToken == taskToken) { mCurrentTaskToken = null; handleNext(); } } private void handleNext() { private void handleNext() { mHandler.removeCallbacks(mDestroyAfterTimeout); mHandler.removeCallbacks(mDestroyAfterTimeout); Pair<Consumer<IBinder>, Executor> next = mTaskQueue.pollFirst(); Consumer<IBinder> next = mTaskQueue.pollFirst(); if (next != null) { if (next != null) { mCurrentTaskToken = next; mOnDestroyTimeout = false; mOnDestroyTimeout = false; next.second.execute(() -> { if (mBinderCallExecutor == null) { next.first.accept(mIBinder); mBinderCallExecutor = createExecutorService( mHandler.post(this::handleNext); mExecutorName, Process.THREAD_PRIORITY_FOREGROUND); } Future task = mBinderCallExecutor.submit(() -> { next.accept(mIBinder); mHandler.post(() -> onTaskComplete(next)); }); }); mHandler.postDelayed( () -> onTaskTimeout(task, next), MAX_ADAPTER_CONVERSION_WAITING_TIME_MS); } else { } else { // Finished all tasks, start a timeout to unbind this service // Finished all tasks, start a timeout to unbind this service mOnDestroyTimeout = true; mOnDestroyTimeout = true; Loading @@ -1699,6 +1784,22 @@ public class AppWidgetManager { } } } } /** * If a task times out, we try to interrupt it, but also switch to a new executor, * in case the previous executor gets blocked for ever */ private void onTaskTimeout(Future task, Object taskToken) { if (!task.isDone()) { ExecutorService oldExecutor = mBinderCallExecutor; mBinderCallExecutor = null; task.cancel(true); if (oldExecutor != null) { oldExecutor.shutdown(); } onTaskComplete(taskToken); } } /** /** * Called after we have waited for {@link #mTimeOut} after the last task is finished * Called after we have waited for {@link #mTimeOut} after the last task is finished */ */ Loading @@ -1712,6 +1813,9 @@ public class AppWidgetManager { } catch (Exception e) { } catch (Exception e) { Log.e(TAG, "Error unbinding the cached connection", e); Log.e(TAG, "Error unbinding the cached connection", e); } } if (mBinderCallExecutor != null) { mBinderCallExecutor.shutdown(); } mActiveConnections.values().remove(this); mActiveConnections.values().remove(this); } } } } Loading
core/java/android/widget/RemoteViews.java +36 −40 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/java/android/widget/RemoteViewsService.java +6 −7 Original line number Original line Diff line number Diff line Loading @@ -130,8 +130,6 @@ public abstract class RemoteViewsService extends Service { */ */ default RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, default RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, int capBitmapSize) { int capBitmapSize) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); Parcel capSizeTestParcel = Parcel.obtain(); Parcel capSizeTestParcel = Parcel.obtain(); // restore allowSquashing to reduce the noise in error messages // restore allowSquashing to reduce the noise in error messages boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); Loading @@ -140,7 +138,6 @@ public abstract class RemoteViewsService extends Service { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = RemoteViews.RemoteCollectionItems.Builder itemsBuilder = new RemoteViews.RemoteCollectionItems.Builder(); new RemoteViews.RemoteCollectionItems.Builder(); RemoteViews.BitmapCache testBitmapCache = null; RemoteViews.BitmapCache testBitmapCache = null; onDataSetChanged(); itemsBuilder.setHasStableIds(hasStableIds()); itemsBuilder.setHasStableIds(hasStableIds()); final int numOfEntries = getCount(); final int numOfEntries = getCount(); Loading @@ -167,14 +164,12 @@ public abstract class RemoteViewsService extends Service { itemsBuilder.addItem(currentItemId, currentView); itemsBuilder.addItem(currentItemId, currentView); } } return itemsBuilder.build(); items = itemsBuilder.build(); } finally { } finally { capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); // Recycle the parcel // Recycle the parcel capSizeTestParcel.recycle(); capSizeTestParcel.recycle(); } } return items; } } } } Loading Loading @@ -282,10 +277,14 @@ public abstract class RemoteViewsService extends Service { @Override @Override public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize, int capBitmapSize) { int capBitmapSize, boolean invalidateData) { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); .Builder().build(); try { try { if (mIsCreated || invalidateData) { mFactory.onDataSetChanged(); mIsCreated = false; } items = mFactory.getRemoteCollectionItems(capSize, capBitmapSize); items = mFactory.getRemoteCollectionItems(capSize, capBitmapSize); } catch (Exception ex) { } catch (Exception ex) { Thread t = Thread.currentThread(); Thread t = Thread.currentThread(); Loading
core/java/com/android/internal/appwidget/IAppWidgetService.aidl +4 −0 Original line number Original line Diff line number Diff line Loading @@ -89,5 +89,9 @@ interface IAppWidgetService { in ComponentName providerComponent, in int profileId, in int widgetCategory); in ComponentName providerComponent, in int profileId, in int widgetCategory); void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories); void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories); oneway void reportWidgetEvents(in String callingPackage, in AppWidgetEvent[] events); oneway void reportWidgetEvents(in String callingPackage, in AppWidgetEvent[] events); // For legacy list migration boolean isFirstConfigActivityPending(in String callingPackage, in int appWidgetId); oneway void setConfigActivityComplete(in int appWidgetId); } }