Add a placeholder empty state message for the bookmarks widget

This CL introduces an 'empty_message' TextView to bookmark_widget.xml and uses partiallyUpdateAppWidget() to efficiently control its visibility based on folder content.

A temporary string is shown in the screenshot, but not checked in. The final string will be provided by UX in a subsequent CL.

https://screenshot.googleplex.com/9FeZimGczYrquZ8

Bug:403557617

Change-Id: Ib006c6eba3b0d5a72d557c7220002fdcfa779866
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6340708
Reviewed-by: Sky Malice <skym@chromium.org>
Commit-Queue: Gang Wu <gangwu@chromium.org>
Reviewed-by: Brandon Wylie <wylieb@google.com>
Cr-Commit-Position: refs/heads/main@{#1432988}
diff --git a/chrome/android/java/res/layout/bookmark_widget.xml b/chrome/android/java/res/layout/bookmark_widget.xml
index b49b71c..e3e5460 100644
--- a/chrome/android/java/res/layout/bookmark_widget.xml
+++ b/chrome/android/java/res/layout/bookmark_widget.xml
@@ -5,14 +5,31 @@
 found in the LICENSE file.
 -->
 
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/bookmarks_list"
-    style="@style/DarkModeCompatibleVerticalScrolling"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/bookmark_widget_background"
-    android:divider="@null"
-    android:drawSelectorOnTop="true"
-    android:listSelector="@drawable/bookmark_widget_list_selector"
-    android:alpha="0.9"
-    android:theme="@style/Theme.Chromium.Widget" />
+    android:theme="@style/Theme.Chromium.Widget"
+    tools:ignore="MergeRootFrame">
+
+    <ListView
+        android:id="@+id/bookmarks_list"
+        style="@style/DarkModeCompatibleVerticalScrolling"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/bookmark_widget_background"
+        android:divider="@null"
+        android:drawSelectorOnTop="true"
+        android:listSelector="@drawable/bookmark_widget_list_selector"
+        android:alpha="0.9" />
+
+    <TextView
+        android:id="@+id/empty_message"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@null"
+        android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+        android:visibility="gone" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java
index f5b11ac..59d2e5a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java
@@ -140,7 +140,7 @@
         }
     }
 
-    private boolean shouldShowIconsOnly(AppWidgetManager appWidgetManager, int appWidgetId) {
+    public static boolean shouldShowIconsOnly(AppWidgetManager appWidgetManager, int appWidgetId) {
         int widthDp =
                 appWidgetManager
                         .getAppWidgetOptions(appWidgetId)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetServiceImpl.java
index 6d3a3a8e1..293a1f6d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetServiceImpl.java
@@ -282,6 +282,7 @@
         private final Context mContext;
         private final int mWidgetId;
         private final SharedPreferences mPreferences;
+        private final RemoteViews mBookmarkWidgeRemoteView;
         private int mIconColor;
 
         // Accessed only on the UI thread
@@ -297,6 +298,8 @@
             mPreferences = getWidgetState(mWidgetId);
             mIconColor = getIconColor(mContext);
             SystemNightModeMonitor.getInstance().addObserver(this);
+            mBookmarkWidgeRemoteView =
+                    new RemoteViews(mContext.getPackageName(), R.layout.bookmark_widget);
         }
 
         @UiThread
@@ -376,7 +379,13 @@
             BookmarkId folderId =
                     BookmarkId.getBookmarkIdFromString(
                             mPreferences.getString(PREF_CURRENT_FOLDER, null));
+
+            // Blocks until bookmarks are loaded from the UI thread.
             mCurrentFolder = loadBookmarks(folderId);
+
+            // Update empty message visibility right after mCurrentFolder is updated.
+            updateFolderEmptyMessageVisibility();
+
             mPreferences
                     .edit()
                     .putString(PREF_CURRENT_FOLDER, mCurrentFolder.folder.id.toString())
@@ -384,6 +393,27 @@
         }
 
         @BinderThread
+        private void updateFolderEmptyMessageVisibility() {
+            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+            if (!BookmarkWidgetProvider.shouldShowIconsOnly(appWidgetManager, mWidgetId)) {
+                boolean folderIsEmpty = mCurrentFolder != null && mCurrentFolder.children.isEmpty();
+                mBookmarkWidgeRemoteView.setViewVisibility(
+                        R.id.empty_message, folderIsEmpty ? View.VISIBLE : View.GONE);
+
+                // Directly update the widget on the UI thread.
+                PostTask.runOrPostTask(
+                        TaskTraits.UI_DEFAULT,
+                        () -> {
+                            // Use AppWidgetManager#partiallyUpdateAppWidget to update only the
+                            // empty_message visibility, avoiding full widget redraws and redundant
+                            // intent setup from BookmarkWidgetProvider#performUpdate.
+                            appWidgetManager.partiallyUpdateAppWidget(
+                                    mWidgetId, mBookmarkWidgeRemoteView);
+                        });
+            }
+        }
+
+        @BinderThread
         private BookmarkFolder loadBookmarks(final BookmarkId folderId) {
             final LinkedBlockingQueue<BookmarkFolder> resultQueue = new LinkedBlockingQueue<>(1);
             // A reference of BookmarkLoader is needed in binder thread to