summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/documentsui/HorizontalBreadcrumb.java39
-rw-r--r--src/com/android/documentsui/JobPanelController.kt126
-rw-r--r--src/com/android/documentsui/MenuManager.java9
-rw-r--r--src/com/android/documentsui/NavigationViewManager.java4
-rw-r--r--src/com/android/documentsui/ProfileTabs.java7
-rw-r--r--src/com/android/documentsui/dirlist/DocumentsSwipeRefreshLayout.java22
-rw-r--r--src/com/android/documentsui/dirlist/GridDocumentHolder.java4
-rw-r--r--src/com/android/documentsui/files/MenuManager.java18
-rw-r--r--src/com/android/documentsui/queries/SearchChipViewManager.java2
-rw-r--r--src/com/android/documentsui/services/FileOperationService.java10
-rw-r--r--src/com/android/documentsui/services/Job.java10
-rw-r--r--src/com/android/documentsui/util/ColorUtils.kt36
12 files changed, 255 insertions, 32 deletions
diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java
index cb25479b3..f47f464c0 100644
--- a/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
+
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
@@ -183,8 +185,6 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru
@Override
public void onBindViewHolder(BreadcrumbHolder holder, int position) {
- final int padding = (int) holder.itemView.getResources()
- .getDimension(R.dimen.breadcrumb_item_padding);
final boolean isFirst = position == 0;
// Note that when isFirst is true, there might not be a DocumentInfo on the stack as it
// could be an error state screen accessible from the root info.
@@ -193,8 +193,39 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru
holder.mTitle.setText(
isFirst ? mEnv.getCurrentRoot().title : mState.stack.get(position).displayName);
holder.mTitle.setEnabled(isLast);
- holder.mTitle.setPadding(isFirst ? padding * 3 : padding,
- padding, isLast ? padding * 2 : padding, padding);
+ if (isUseMaterial3FlagEnabled()) {
+ final int paddingHorizontal =
+ (int)
+ holder.itemView
+ .getResources()
+ .getDimension(R.dimen.breadcrumb_item_padding_horizontal);
+ final int paddingVertical =
+ (int)
+ holder.itemView
+ .getResources()
+ .getDimension(R.dimen.breadcrumb_item_padding_vertical);
+ final int arrowPadding =
+ (int)
+ holder.itemView
+ .getResources()
+ .getDimension(R.dimen.breadcrumb_item_arrow_padding);
+ holder.mTitle.setPadding(
+ paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
+
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) holder.mArrow.getLayoutParams();
+ params.setMarginStart(arrowPadding);
+ params.setMarginEnd(arrowPadding);
+ holder.mArrow.setLayoutParams(params);
+ } else {
+ final int padding = (int) holder.itemView.getResources()
+ .getDimension(R.dimen.breadcrumb_item_padding);
+ holder.mTitle.setPadding(
+ isFirst ? padding * 3 : padding,
+ padding,
+ isLast ? padding * 2 : padding,
+ padding);
+ }
holder.mArrow.setVisibility(isLast ? View.GONE : View.VISIBLE);
holder.itemView.setOnKeyListener(mClickListener);
diff --git a/src/com/android/documentsui/JobPanelController.kt b/src/com/android/documentsui/JobPanelController.kt
new file mode 100644
index 000000000..b3b5f1cfd
--- /dev/null
+++ b/src/com/android/documentsui/JobPanelController.kt
@@ -0,0 +1,126 @@
+/*
+ * 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 com.android.documentsui
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.util.Log
+import android.view.MenuItem
+import android.widget.ProgressBar
+import com.android.documentsui.base.Menus
+import com.android.documentsui.services.FileOperationService
+import com.android.documentsui.services.FileOperationService.EXTRA_PROGRESS
+import com.android.documentsui.services.Job
+import com.android.documentsui.services.JobProgress
+
+/**
+ * JobPanelController is responsible for receiving broadcast updates from the [FileOperationService]
+ * and updating a given menu item to reflect the current progress.
+ */
+class JobPanelController(private val mContext: Context) : BroadcastReceiver() {
+ companion object {
+ private const val TAG = "JobPanelController"
+ private const val MAX_PROGRESS = 100
+ }
+
+ private enum class State {
+ INVISIBLE, INDETERMINATE, VISIBLE
+ }
+
+ /** The current state of the menu progress item. */
+ private var mState = State.INVISIBLE
+
+ /** The total progress from 0 to MAX_PROGRESS. */
+ private var mTotalProgress = 0
+
+ /** List of jobs currently tracked by this class. */
+ private val mCurrentJobs = LinkedHashMap<String, JobProgress>()
+
+ /** Current menu item being controlled by this class. */
+ private var mMenuItem: MenuItem? = null
+
+ init {
+ val filter = IntentFilter(FileOperationService.ACTION_PROGRESS)
+ mContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED)
+ }
+
+ private fun updateMenuItem(animate: Boolean) {
+ mMenuItem?.let {
+ Menus.setEnabledAndVisible(it, mState != State.INVISIBLE)
+ val icon = it.actionView as ProgressBar
+ when (mState) {
+ State.INDETERMINATE -> icon.isIndeterminate = true
+ State.VISIBLE -> icon.apply {
+ isIndeterminate = false
+ setProgress(mTotalProgress, animate)
+ }
+ State.INVISIBLE -> {}
+ }
+ }
+ }
+
+ /**
+ * Sets the menu item controlled by this class. The item's actionView must be a [ProgressBar].
+ */
+ fun setMenuItem(menuItem: MenuItem) {
+ (menuItem.actionView as ProgressBar).max = MAX_PROGRESS
+ mMenuItem = menuItem
+ updateMenuItem(animate = false)
+ }
+
+ override fun onReceive(context: Context?, intent: Intent) {
+ val progresses = intent.getParcelableArrayListExtra<JobProgress>(
+ EXTRA_PROGRESS,
+ JobProgress::class.java
+ )
+ updateProgress(progresses!!)
+ }
+
+ private fun updateProgress(progresses: List<JobProgress>) {
+ var requiredBytes = 0L
+ var currentBytes = 0L
+ var allFinished = true
+
+ for (jobProgress in progresses) {
+ Log.d(TAG, "Received $jobProgress")
+ mCurrentJobs.put(jobProgress.id, jobProgress)
+ }
+ for (jobProgress in mCurrentJobs.values) {
+ if (jobProgress.state != Job.STATE_COMPLETED) {
+ allFinished = false
+ }
+ if (jobProgress.requiredBytes != -1L && jobProgress.currentBytes != -1L) {
+ requiredBytes += jobProgress.requiredBytes
+ currentBytes += jobProgress.currentBytes
+ }
+ }
+
+ if (mCurrentJobs.isEmpty()) {
+ mState = State.INVISIBLE
+ } else if (requiredBytes != 0L) {
+ mState = State.VISIBLE
+ mTotalProgress = (MAX_PROGRESS * currentBytes / requiredBytes).toInt()
+ } else if (allFinished) {
+ mState = State.VISIBLE
+ mTotalProgress = MAX_PROGRESS
+ } else {
+ mState = State.INDETERMINATE
+ }
+ updateMenuItem(animate = true)
+ }
+}
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 888a45996..a46659c77 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -282,6 +282,15 @@ public abstract class MenuManager {
public abstract void updateKeyboardShortcutsMenu(
List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier);
+ /**
+ * Called on option menu creation to instantiate the job progress item if applicable.
+ *
+ * @param menu The option menu created.
+ */
+ public void instantiateJobProgress(Menu menu) {
+ // This icon is not shown in the picker.
+ }
+
protected void updateModePicker(MenuItem grid, MenuItem list) {
// The order of enabling disabling menu item in wrong order removed accessibility focus.
if (mState.derivedMode != State.MODE_LIST) {
diff --git a/src/com/android/documentsui/NavigationViewManager.java b/src/com/android/documentsui/NavigationViewManager.java
index 12afbd69b..bbae22d42 100644
--- a/src/com/android/documentsui/NavigationViewManager.java
+++ b/src/com/android/documentsui/NavigationViewManager.java
@@ -18,6 +18,7 @@ package com.android.documentsui;
import static com.android.documentsui.base.SharedMinimal.VERBOSE;
import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
+import static com.android.documentsui.util.FlagUtils.isVisualSignalsFlagEnabled;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -409,6 +410,9 @@ public class NavigationViewManager extends SelectionTracker.SelectionObserver<St
mActivity.getResources().getBoolean(R.bool.full_bar_search_view);
boolean showSearchBar = mActivity.getResources().getBoolean(R.bool.show_search_bar);
mInjector.searchManager.install(mToolbar.getMenu(), fullBarSearch, showSearchBar);
+ if (isVisualSignalsFlagEnabled()) {
+ mInjector.menuManager.instantiateJobProgress(mToolbar.getMenu());
+ }
}
mInjector.menuManager.updateOptionMenu(mToolbar.getMenu());
mInjector.searchManager.showMenu(mState.stack);
diff --git a/src/com/android/documentsui/ProfileTabs.java b/src/com/android/documentsui/ProfileTabs.java
index 5aacc22b0..74db6f4bd 100644
--- a/src/com/android/documentsui/ProfileTabs.java
+++ b/src/com/android/documentsui/ProfileTabs.java
@@ -157,9 +157,12 @@ public class ProfileTabs implements ProfileTabsAddons {
int tabMarginSide = (int) mTabsContainer.getContext().getResources()
.getDimension(R.dimen.profile_tab_margin_side);
if (isUseMaterial3FlagEnabled()) {
- // M3 uses the margin value as the right margin, except for the last child.
+ final boolean isRtl = mTabs.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ // if use_material3 flag is ON, we uses the margin value as the right margin
+ // (left margin for RTL), except for the last child.
if (i != mTabs.getTabCount() - 1) {
- marginLayoutParams.setMargins(0, 0, tabMarginSide, 0);
+ marginLayoutParams.setMargins(
+ isRtl ? tabMarginSide : 0, 0, isRtl ? 0 : tabMarginSide, 0);
}
} else {
marginLayoutParams.setMargins(tabMarginSide, 0, tabMarginSide, 0);
diff --git a/src/com/android/documentsui/dirlist/DocumentsSwipeRefreshLayout.java b/src/com/android/documentsui/dirlist/DocumentsSwipeRefreshLayout.java
index 838b1fa72..64409673e 100644
--- a/src/com/android/documentsui/dirlist/DocumentsSwipeRefreshLayout.java
+++ b/src/com/android/documentsui/dirlist/DocumentsSwipeRefreshLayout.java
@@ -22,13 +22,13 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
import android.view.MotionEvent;
import androidx.annotation.ColorRes;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.android.documentsui.R;
+import com.android.documentsui.util.ColorUtils;
/**
* A {@link SwipeRefreshLayout} that does not intercept any touch events. This relies on its nested
@@ -46,20 +46,12 @@ public class DocumentsSwipeRefreshLayout extends SwipeRefreshLayout {
super(context, attrs);
if (isUseMaterial3FlagEnabled()) {
- TypedValue spinnerColor = new TypedValue();
- context.getTheme()
- .resolveAttribute(
- com.google.android.material.R.attr.colorOnPrimaryContainer,
- spinnerColor,
- true);
- setColorSchemeResources(spinnerColor.resourceId);
- TypedValue spinnerBackgroundColor = new TypedValue();
- context.getTheme()
- .resolveAttribute(
- com.google.android.material.R.attr.colorPrimaryContainer,
- spinnerBackgroundColor,
- true);
- setProgressBackgroundColorSchemeResource(spinnerBackgroundColor.resourceId);
+ setColorSchemeColors(
+ ColorUtils.resolveMaterialColorAttribute(
+ context, com.google.android.material.R.attr.colorOnPrimaryContainer));
+ setProgressBackgroundColorSchemeColor(
+ ColorUtils.resolveMaterialColorAttribute(
+ context, com.google.android.material.R.attr.colorPrimaryContainer));
} else {
final int[] styledAttrs = {android.R.attr.colorAccent};
diff --git a/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/src/com/android/documentsui/dirlist/GridDocumentHolder.java
index 72ba1c3f9..67ee1f2bf 100644
--- a/src/com/android/documentsui/dirlist/GridDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/GridDocumentHolder.java
@@ -307,8 +307,8 @@ final class GridDocumentHolder extends DocumentHolder {
}
}
- if (mBullet != null && (mDetails.getVisibility() == View.GONE
- || mDate.getText().isEmpty())) {
+ if (mBullet != null && (mDetails.getText() == null || mDetails.getText().length() == 0
+ || mDate.getText() == null || mDate.getText().length() == 0)) {
// There is no need for the bullet separating the details and date.
mBullet.setVisibility(View.GONE);
}
diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java
index 7dc6b57d6..2c85dcb01 100644
--- a/src/com/android/documentsui/files/MenuManager.java
+++ b/src/com/android/documentsui/files/MenuManager.java
@@ -17,6 +17,7 @@
package com.android.documentsui.files;
import static com.android.documentsui.util.FlagUtils.isDesktopFileHandlingFlagEnabled;
+import static com.android.documentsui.util.FlagUtils.isVisualSignalsFlagEnabled;
import android.content.Context;
import android.content.res.Resources;
@@ -30,9 +31,11 @@ import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.selection.SelectionTracker;
+import com.android.documentsui.JobPanelController;
import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Features;
@@ -56,6 +59,7 @@ public final class MenuManager extends com.android.documentsui.MenuManager {
private final SelectionTracker<String> mSelectionManager;
private final Lookup<String, Uri> mUriLookup;
private final LookupApplicationName mAppNameLookup;
+ @Nullable private final JobPanelController mJobPanelController;
public MenuManager(
Features features,
@@ -75,6 +79,12 @@ public final class MenuManager extends com.android.documentsui.MenuManager {
mSelectionManager = selectionManager;
mAppNameLookup = appNameLookup;
mUriLookup = uriLookup;
+
+ if (isVisualSignalsFlagEnabled()) {
+ mJobPanelController = new JobPanelController(context);
+ } else {
+ mJobPanelController = null;
+ }
}
@Override
@@ -142,6 +152,14 @@ public final class MenuManager extends com.android.documentsui.MenuManager {
}
@Override
+ public void instantiateJobProgress(Menu menu) {
+ if (mJobPanelController == null) {
+ return;
+ }
+ mJobPanelController.setMenuItem(menu.findItem(R.id.option_menu_job_progress));
+ }
+
+ @Override
protected void updateSettings(MenuItem settings, RootInfo root) {
Menus.setEnabledAndVisible(settings, root.hasSettings());
}
diff --git a/src/com/android/documentsui/queries/SearchChipViewManager.java b/src/com/android/documentsui/queries/SearchChipViewManager.java
index c2403dd03..bf3d1e865 100644
--- a/src/com/android/documentsui/queries/SearchChipViewManager.java
+++ b/src/com/android/documentsui/queries/SearchChipViewManager.java
@@ -521,7 +521,7 @@ public class SearchChipViewManager {
}
// Let the first checked chip can be shown.
- View parent = (View) mChipGroup.getParent();
+ View parent = (View) mChipGroup.getParent().getParent();
if (parent instanceof HorizontalScrollView) {
final int scrollToX = isRtl ? parent.getWidth() : 0;
((HorizontalScrollView) parent).smoothScrollTo(scrollToX, 0);
diff --git a/src/com/android/documentsui/services/FileOperationService.java b/src/com/android/documentsui/services/FileOperationService.java
index dcb2c1db4..14b87ef5b 100644
--- a/src/com/android/documentsui/services/FileOperationService.java
+++ b/src/com/android/documentsui/services/FileOperationService.java
@@ -68,6 +68,9 @@ public class FileOperationService extends Service implements Job.Listener {
public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION";
public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
+ public static final String ACTION_PROGRESS = "com.android.documentsui.action.PROGRESS";
+ public static final String EXTRA_PROGRESS = "com.android.documentsui.PROGRESS";
+
@IntDef({
OPERATION_UNKNOWN,
OPERATION_COPY,
@@ -580,6 +583,7 @@ public class FileOperationService extends Service implements Job.Listener {
private final class GlobalJobMonitor implements Runnable {
private static final long PROGRESS_INTERVAL_MILLIS = 500L;
private boolean mRunning = false;
+ private long mLastId = 0;
private void start() {
if (!mRunning) {
@@ -602,9 +606,9 @@ public class FileOperationService extends Service implements Job.Listener {
}
Intent intent = new Intent();
intent.setPackage(getPackageName());
- intent.setAction("com.android.documentsui.PROGRESS");
- intent.putExtra("id", 0);
- intent.putParcelableArrayListExtra("progress", progress);
+ intent.setAction(ACTION_PROGRESS);
+ intent.putExtra("id", mLastId++);
+ intent.putParcelableArrayListExtra(EXTRA_PROGRESS, progress);
sendBroadcast(intent);
}
diff --git a/src/com/android/documentsui/services/Job.java b/src/com/android/documentsui/services/Job.java
index 0f432cc19..95ad8b335 100644
--- a/src/com/android/documentsui/services/Job.java
+++ b/src/com/android/documentsui/services/Job.java
@@ -76,11 +76,11 @@ abstract public class Job implements Runnable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_CREATED, STATE_STARTED, STATE_SET_UP, STATE_COMPLETED, STATE_CANCELED})
- @interface State {}
- static final int STATE_CREATED = 0;
- static final int STATE_STARTED = 1;
- static final int STATE_SET_UP = 2;
- static final int STATE_COMPLETED = 3;
+ public @interface State {}
+ public static final int STATE_CREATED = 0;
+ public static final int STATE_STARTED = 1;
+ public static final int STATE_SET_UP = 2;
+ public static final int STATE_COMPLETED = 3;
/**
* A job is in canceled state as long as {@link #cancel()} is called on it, even after it is
* completed.
diff --git a/src/com/android/documentsui/util/ColorUtils.kt b/src/com/android/documentsui/util/ColorUtils.kt
new file mode 100644
index 000000000..ee67b7832
--- /dev/null
+++ b/src/com/android/documentsui/util/ColorUtils.kt
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.documentsui.util
+
+import android.content.Context
+import android.util.TypedValue
+import androidx.annotation.AttrRes
+
+class ColorUtils {
+ companion object {
+ /**
+ * Resolve a color attribute from the Material3 theme, example usage.
+ * resolveMaterialColorAttribute(context, com.google.android.material.R.attr.XXX).
+ */
+ @JvmStatic
+ fun resolveMaterialColorAttribute(context: Context, @AttrRes colorAttrId: Int): Int {
+ val typedValue = TypedValue()
+ context.theme.resolveAttribute(colorAttrId, typedValue, true)
+ return typedValue.data
+ }
+ }
+}