diff options
author | 2025-03-14 16:11:39 +0000 | |
---|---|---|
committer | 2025-03-20 00:17:48 +0000 | |
commit | 4332f355c63fbfc4bc7c859ce8969b1960dc39dd (patch) | |
tree | 7fe095c76b8ac0fe30d833cdc605a0131b9e2c4e | |
parent | 60ab4dfb4e8814d1da61ba450cd228b4771b84bf (diff) |
Add the job progress panel
This panel is displayed on click of the job toolbar icon.
This also changes the context passed into files.MenuManager to be the
activity context as the application context doesn't have the theme
information required for JobPanelController to inflate layouts.
Bug: 385841754
Test: atest -c 'DocumentsUIGoogleTests:com.android.documentsui.JobPanelUiTest'
Flag: com.android.documentsui.flags.visual_signals_ro
Change-Id: Idd95bd97863892407ed740b3ff8a013408e37b6a
12 files changed, 215 insertions, 15 deletions
diff --git a/proguard.flags b/proguard.flags index 76449d4e9..34071fa6d 100644 --- a/proguard.flags +++ b/proguard.flags @@ -106,6 +106,7 @@ int dir_menu_view_in_owner; int drawer_layout; int inspector_details_view; + int job_progress_panel_title; int option_menu_create_dir; int option_menu_debug; int option_menu_extract_all; diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_panel.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_panel.xml new file mode 100644 index 000000000..17f6aa6fc --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/job_progress_panel.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.google.android.material.card.MaterialCardView + style="@style/JobProgressPanelStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/job_progress_panel_title" + android:text="@string/job_progress_panel_title" + android:textAppearance="@style/JobProgressPanelHeaderText" + android:layout_margin="@dimen/job_progress_panel_header_margin" /> + <androidx.recyclerview.widget.RecyclerView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/job_progress_list" /> + </LinearLayout> + </com.google.android.material.card.MaterialCardView> +</FrameLayout> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml index ed94fd16f..c679e64a3 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml @@ -240,4 +240,8 @@ <dimen name="focus_ring_gap">5dp</dimen> <dimen name="hover_overlay_alpha">0.08</dimen> <dimen name="ripple_overlay_alpha">0.10</dimen> + + <dimen name="job_progress_panel_width">360dp</dimen> + <dimen name="job_progress_panel_margin">@dimen/space_small_1</dimen> + <dimen name="job_progress_panel_header_margin">@dimen/space_small_1</dimen> </resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml index 6819c12a3..481cd0be8 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml @@ -190,4 +190,11 @@ <item name="android:textColor">@color/nav_rail_item_text_color</item> <item name="android:textAppearance">@style/NavRailItemTextAppearance</item> </style> + + <style name="JobProgressPanelStyle" parent="@style/Widget.Material3.CardView.Elevated"> + <item name="android:layout_marginStart">@dimen/job_progress_panel_margin</item> + <item name="android:layout_marginBottom">@dimen/job_progress_panel_margin</item> + <item name="cardElevation">1dp</item> + <item name="cardBackgroundColor">?attr/colorSurfaceDim</item> + </style> </resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml index 37dd8a135..92ec6b8ae 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml @@ -160,4 +160,8 @@ <item name="fontFamily">@string/config_fontFamily</item> </style> + <style name="JobProgressPanelHeaderText" parent="@style/TextAppearance.Material3.TitleMedium"> + <item name="fontFamily">@string/config_fontFamilyMedium</item> + </style> + </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index f8ade4c47..c3f11bab4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -429,6 +429,7 @@ =1 {Zipping <xliff:g id="filename" example="foobar.txt">{filename}</xliff:g>} other {Zipping # files} }</string> + <string name="job_progress_panel_title" translatable="false">File Progress</string> <!-- Text in an alert dialog asking user to grant app access to a given directory in an external storage volume --> <string name="open_external_dialog_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g> diff --git a/src/com/android/documentsui/JobPanelController.kt b/src/com/android/documentsui/JobPanelController.kt index b3b5f1cfd..a8ab8b0a4 100644 --- a/src/com/android/documentsui/JobPanelController.kt +++ b/src/com/android/documentsui/JobPanelController.kt @@ -20,7 +20,10 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.util.Log +import android.view.LayoutInflater import android.view.MenuItem +import android.view.ViewGroup +import android.widget.PopupWindow import android.widget.ProgressBar import com.android.documentsui.base.Menus import com.android.documentsui.services.FileOperationService @@ -77,8 +80,29 @@ class JobPanelController(private val mContext: Context) : BroadcastReceiver() { /** * Sets the menu item controlled by this class. The item's actionView must be a [ProgressBar]. */ + @Suppress("ktlint:standard:comment-wrapping") fun setMenuItem(menuItem: MenuItem) { - (menuItem.actionView as ProgressBar).max = MAX_PROGRESS + val progressIcon = menuItem.actionView as ProgressBar + progressIcon.max = MAX_PROGRESS + progressIcon.setOnClickListener { view -> + val panel = LayoutInflater.from(mContext).inflate( + R.layout.job_progress_panel, + /* root= */ null + ) + val popupWidth = mContext.resources.getDimension(R.dimen.job_progress_panel_width) + + mContext.resources.getDimension(R.dimen.job_progress_panel_margin) + val popup = PopupWindow( + /* contentView= */ panel, + /* width= */ popupWidth.toInt(), + /* height= */ ViewGroup.LayoutParams.WRAP_CONTENT, + /* focusable= */ true + ) + popup.showAsDropDown( + /* anchor= */ view, + /* xoff= */ view.width - popupWidth.toInt(), + /* yoff= */ 0 + ) + } mMenuItem = menuItem updateMenuItem(animate = false) } diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index 50e266d38..5d2c37b55 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -19,6 +19,7 @@ package com.android.documentsui.files; import static com.android.documentsui.OperationDialogFragment.DIALOG_TYPE_UNKNOWN; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled; +import static com.android.documentsui.util.FlagUtils.isVisualSignalsFlagEnabled; import static com.android.documentsui.util.FlagUtils.isZipNgFlagEnabled; import android.app.ActivityManager.TaskDescription; @@ -134,7 +135,7 @@ public class FilesActivity extends BaseActivity implements AbstractActionHandler return clipper.hasItemsToPaste(); } }, - getApplicationContext(), + isVisualSignalsFlagEnabled() ? this : getApplicationContext(), mInjector.selectionMgr, mProviders::getApplicationName, mInjector.getModel()::getItemUri, diff --git a/tests/Android.bp b/tests/Android.bp index 41ccc1ab1..95418bdde 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -70,6 +70,7 @@ android_library { srcs: [ "common/**/*.java", + "common/**/*.kt", "unit/**/*.java", "unit/**/*.kt", ], @@ -94,6 +95,7 @@ android_library { srcs: [ "common/**/*.java", + "common/**/*.kt", "functional/**/*.java", "functional/**/*.kt", "unit/**/*.java", diff --git a/tests/common/com/android/documentsui/testing/MutableJobProgress.kt b/tests/common/com/android/documentsui/testing/MutableJobProgress.kt new file mode 100644 index 000000000..b2dd595f9 --- /dev/null +++ b/tests/common/com/android/documentsui/testing/MutableJobProgress.kt @@ -0,0 +1,32 @@ +/* + * 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.testing + +import com.android.documentsui.services.Job +import com.android.documentsui.services.JobProgress + +data class MutableJobProgress( + var id: String, + @Job.State var state: Int, + var msg: String?, + var hasFailures: Boolean, + var currentBytes: Long = -1, + var requiredBytes: Long = -1, + var msRemaining: Long = -1, +) { + fun toJobProgress() = + JobProgress(id, state, msg, hasFailures, currentBytes, requiredBytes, msRemaining) +} diff --git a/tests/functional/com/android/documentsui/JobPanelUiTest.kt b/tests/functional/com/android/documentsui/JobPanelUiTest.kt new file mode 100644 index 000000000..5b28b1f5d --- /dev/null +++ b/tests/functional/com/android/documentsui/JobPanelUiTest.kt @@ -0,0 +1,93 @@ +/* + * 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.Intent +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.documentsui.files.FilesActivity +import com.android.documentsui.flags.Flags.FLAG_USE_MATERIAL3 +import com.android.documentsui.flags.Flags.FLAG_VISUAL_SIGNALS_RO +import com.android.documentsui.services.FileOperationService.ACTION_PROGRESS +import com.android.documentsui.services.FileOperationService.EXTRA_PROGRESS +import com.android.documentsui.services.Job +import com.android.documentsui.services.JobProgress +import com.android.documentsui.testing.MutableJobProgress +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RequiresFlagsEnabled(FLAG_USE_MATERIAL3, FLAG_VISUAL_SIGNALS_RO) +@RunWith(AndroidJUnit4::class) +class JobPanelUiTest : ActivityTestJunit4<FilesActivity>() { + @get:Rule + val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + private var mLastId = 0L + + private fun sendProgress(progresses: ArrayList<JobProgress>, id: Long = mLastId++) { + val context = InstrumentationRegistry.getInstrumentation().targetContext + var intent = Intent(ACTION_PROGRESS).apply { + `package` = context.packageName + putExtra("id", id) + putParcelableArrayListExtra(EXTRA_PROGRESS, progresses) + } + context.sendBroadcast(intent) + } + + @Before + override fun setUp() { + super.setUp() + } + + @After + override fun tearDown() { + super.tearDown() + } + + @Test + fun testJobPanelAppearsOnClick() { + onView(withId(R.id.option_menu_job_progress)).check(doesNotExist()) + onView(withId(R.id.job_progress_panel_title)).check(doesNotExist()) + + val progress = MutableJobProgress( + id = "jobId1", + state = Job.STATE_SET_UP, + msg = "Job started", + hasFailures = false, + currentBytes = 4, + requiredBytes = 10, + msRemaining = -1 + ) + sendProgress(arrayListOf(progress.toJobProgress())) + + onView(withId(R.id.option_menu_job_progress)) + .check(matches(isDisplayed())) + .perform(click()) + onView(withId(R.id.job_progress_panel_title)).check(matches(isDisplayed())) + } +} diff --git a/tests/unit/com/android/documentsui/JobPanelControllerTest.kt b/tests/unit/com/android/documentsui/JobPanelControllerTest.kt index be0c9adbd..3e510edd9 100644 --- a/tests/unit/com/android/documentsui/JobPanelControllerTest.kt +++ b/tests/unit/com/android/documentsui/JobPanelControllerTest.kt @@ -30,6 +30,7 @@ import com.android.documentsui.services.FileOperationService.ACTION_PROGRESS import com.android.documentsui.services.FileOperationService.EXTRA_PROGRESS import com.android.documentsui.services.Job import com.android.documentsui.services.JobProgress +import com.android.documentsui.testing.MutableJobProgress import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue @@ -38,19 +39,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -private data class MutableJobProgress( - var id: String, - @Job.State var state: Int, - var msg: String?, - var hasFailures: Boolean, - var currentBytes: Long = -1, - var requiredBytes: Long = -1, - var msRemaining: Long = -1, -) { - fun toJobProgress() = - JobProgress(id, state, msg, hasFailures, currentBytes, requiredBytes, msRemaining) -} - @SmallTest @RequiresFlagsEnabled(FLAG_USE_MATERIAL3, FLAG_VISUAL_SIGNALS_RO) @RunWith(AndroidJUnit4::class) |