diff options
28 files changed, 756 insertions, 302 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/drawer_layout.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/drawer_layout.xml index aeb85442f..0fe74fe64 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/layout/drawer_layout.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/drawer_layout.xml @@ -116,4 +116,12 @@ </LinearLayout> </androidx.drawerlayout.widget.DrawerLayout> + + <!-- Peek overlay --> + <FrameLayout + android:id="@+id/peek_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> + </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/fixed_layout.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/fixed_layout.xml index c2a06122a..4445dd1a6 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/layout/fixed_layout.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/fixed_layout.xml @@ -131,4 +131,11 @@ </LinearLayout> + <!-- Peek overlay --> + <FrameLayout + android:id="@+id/peek_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> + </androidx.coordinatorlayout.widget.CoordinatorLayout> 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)/layout/nav_rail_layout.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_layout.xml index bfbb83c17..091444155 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_layout.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_layout.xml @@ -179,4 +179,12 @@ </LinearLayout> </androidx.drawerlayout.widget.DrawerLayout> + + <!-- Peek overlay --> + <FrameLayout + android:id="@+id/peek_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> + </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/peek_layout.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/peek_layout.xml new file mode 100644 index 000000000..50102d622 --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/peek_layout.xml @@ -0,0 +1,22 @@ +<?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. +--> + +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/peek_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/peek_overlay_background" + android:focusable="false" /> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml index fe98c92b5..5696288e6 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/colors.xml @@ -91,4 +91,8 @@ </shape> --> <color name="overlay_hover_color_percentage">#14000000</color> <!-- 8% --> + + <!-- Peek overlay static color. Makes the background dimmer with an 80% opacity. This color is not + intended to be dynamic, and is defined specifically for Peek. --> + <color name="peek_overlay_background">#CC000000</color> <!-- 80% --> </resources> 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 f6cff73b4..fa9e436ab 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 @@ -248,4 +248,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 2f400ea24..57dbb1212 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 @@ -159,4 +159,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/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index 790feeac4..8a5779a69 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -20,6 +20,7 @@ import static com.android.documentsui.base.Shared.EXTRA_BENCHMARK; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.State.MODE_GRID; import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled; +import static com.android.documentsui.util.FlagUtils.isUsePeekPreviewFlagEnabled; import android.content.Context; import android.content.Intent; @@ -65,6 +66,7 @@ import com.android.documentsui.base.UserId; import com.android.documentsui.dirlist.AnimationView; import com.android.documentsui.dirlist.AppsRowManager; import com.android.documentsui.dirlist.DirectoryFragment; +import com.android.documentsui.peek.PeekViewManager; import com.android.documentsui.prefs.LocalPreferences; import com.android.documentsui.prefs.PreferencesMonitor; import com.android.documentsui.queries.CommandInterceptor; @@ -96,6 +98,7 @@ public abstract class BaseActivity protected SearchViewManager mSearchManager; protected AppsRowManager mAppsRowManager; + protected @Nullable PeekViewManager mPeekViewManager; protected UserIdManager mUserIdManager; protected UserManagerState mUserManagerState; protected State mState; @@ -414,6 +417,11 @@ public abstract class BaseActivity // Base classes must update result in their onCreate. setResult(AppCompatActivity.RESULT_CANCELED); updateRecentsSetting(); + + if (isUsePeekPreviewFlagEnabled()) { + mPeekViewManager = new PeekViewManager(this); + mPeekViewManager.initFragment(getSupportFragmentManager()); + } } private NavigationViewManager getNavigationViewManager(Breadcrumb breadcrumb, 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/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java index 86f7a1a14..cbe02dc25 100644 --- a/src/com/android/documentsui/files/ActionHandler.java +++ b/src/com/android/documentsui/files/ActionHandler.java @@ -71,6 +71,7 @@ import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.clipping.UrisSupplier; import com.android.documentsui.dirlist.AnimationView; import com.android.documentsui.inspector.InspectorActivity; +import com.android.documentsui.peek.PeekViewManager; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.roots.ProvidersAccess; import com.android.documentsui.services.FileOperation; @@ -101,6 +102,7 @@ public class ActionHandler<T extends FragmentActivity & AbstractActionHandler.Co private final ClipStore mClipStore; private final DragAndDropManager mDragAndDropManager; private final Runnable mCloseSelectionBar; + private final @Nullable PeekViewManager mPeekViewManager; ActionHandler( T activity, @@ -114,6 +116,7 @@ public class ActionHandler<T extends FragmentActivity & AbstractActionHandler.Co DocumentClipper clipper, ClipStore clipStore, DragAndDropManager dragAndDropManager, + @Nullable PeekViewManager peekViewManager, Injector injector) { super(activity, state, providers, docs, searchMgr, executors, injector); @@ -125,6 +128,7 @@ public class ActionHandler<T extends FragmentActivity & AbstractActionHandler.Co mClipper = clipper; mClipStore = clipStore; mDragAndDropManager = dragAndDropManager; + mPeekViewManager = peekViewManager; } @Override @@ -609,14 +613,16 @@ public class ActionHandler<T extends FragmentActivity & AbstractActionHandler.Co mActivity.startActivity(intent); } - private void showPeek() { - Log.d(TAG, "Peek not implemented"); + private void showPeek(DocumentInfo doc) { + if (mPeekViewManager != null) { + mPeekViewManager.peekDocument(doc); + } } @Override public void showPreview(DocumentInfo doc) { - if (isUseMaterial3FlagEnabled() && isUsePeekPreviewFlagEnabled()) { - showPeek(); + if (isUsePeekPreviewFlagEnabled()) { + showPeek(doc); } else { showInspector(doc); } diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index cb8708f0b..b254ce525 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, @@ -163,6 +164,7 @@ public class FilesActivity extends BaseActivity implements AbstractActionHandler clipper, DocumentsApplication.getClipStore(this), DocumentsApplication.getDragAndDropManager(this), + mPeekViewManager, mInjector); mInjector.searchManager = mSearchManager; diff --git a/src/com/android/documentsui/peek/PeekFragment.kt b/src/com/android/documentsui/peek/PeekFragment.kt new file mode 100644 index 000000000..50ee64efc --- /dev/null +++ b/src/com/android/documentsui/peek/PeekFragment.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.peek + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment + +import com.android.documentsui.R + +class PeekFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.peek_layout, container, /* attachToRoot= */ false) + } +} diff --git a/src/com/android/documentsui/peek/PeekViewManager.kt b/src/com/android/documentsui/peek/PeekViewManager.kt new file mode 100644 index 000000000..9bbeba3bf --- /dev/null +++ b/src/com/android/documentsui/peek/PeekViewManager.kt @@ -0,0 +1,83 @@ +/* + * 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.peek + +import android.app.Activity +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.FrameLayout +import androidx.annotation.IdRes +import androidx.fragment.app.FragmentManager +import com.android.documentsui.R +import androidx.fragment.app.FragmentTransaction +import com.android.documentsui.base.DocumentInfo +import com.android.documentsui.util.FlagUtils.Companion.isUsePeekPreviewFlagEnabled + +/** + * Manager that controls the Peek UI. + */ +open class PeekViewManager( + private val mActivity: Activity +) { + companion object { + const val TAG = "PeekViewManager" + } + + private var mPeekFragment: PeekFragment? = null + + open fun initFragment( + fm: FragmentManager + ) { + if (!isUsePeekPreviewFlagEnabled()) { + Log.e(TAG, "Attempting to create PeekViewManager while Peek disabled") + return + } + + if (getOverlayContainer() == null) { + Log.e(TAG, "Unable to find Peek container") + return + } + + // Load the Peek fragment into its container. + val peekFragment = PeekFragment() + mPeekFragment = peekFragment + val ft: FragmentTransaction = fm.beginTransaction() + ft.replace(getOverlayId(), peekFragment) + ft.commitAllowingStateLoss() + } + + open fun peekDocument(doc: DocumentInfo) { + if (mPeekFragment == null) { + Log.e(TAG, "Peek fragment not initialized") + return + } + show() + } + + @IdRes + private fun getOverlayId(): Int { + return R.id.peek_overlay + } + + private fun getOverlayContainer(): FrameLayout? { + return mActivity.findViewById(getOverlayId()) + } + + private fun show() { + getOverlayContainer()?.visibility = View.VISIBLE + } +}
\ No newline at end of file diff --git a/src/com/android/documentsui/util/FlagUtils.kt b/src/com/android/documentsui/util/FlagUtils.kt index a041dde44..cf81d5966 100644 --- a/src/com/android/documentsui/util/FlagUtils.kt +++ b/src/com/android/documentsui/util/FlagUtils.kt @@ -56,7 +56,7 @@ class FlagUtils { @JvmStatic fun isUsePeekPreviewFlagEnabled(): Boolean { - return Flags.usePeekPreviewRo() + return Flags.usePeekPreviewRo() && isUseMaterial3FlagEnabled() } } } diff --git a/tests/Android.bp b/tests/Android.bp index 41ccc1ab1..65e3f259f 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -42,6 +42,7 @@ android_library { name: "DocumentsUIPerfTests-lib", srcs: [ "common/com/android/documentsui/**/*.java", + "common/com/android/documentsui/**/*.kt", "functional/com/android/documentsui/ActivityTest.java", ], resource_dirs: [], @@ -70,6 +71,7 @@ android_library { srcs: [ "common/**/*.java", + "common/**/*.kt", "unit/**/*.java", "unit/**/*.kt", ], @@ -94,6 +96,7 @@ android_library { srcs: [ "common/**/*.java", + "common/**/*.kt", "functional/**/*.java", "functional/**/*.kt", "unit/**/*.java", diff --git a/tests/common/com/android/documentsui/bots/Bots.java b/tests/common/com/android/documentsui/bots/Bots.java index 8cc00ac7a..f0f69b9cf 100644 --- a/tests/common/com/android/documentsui/bots/Bots.java +++ b/tests/common/com/android/documentsui/bots/Bots.java @@ -48,6 +48,7 @@ public final class Bots { public final UiBot main; public final InspectorBot inspector; public final NotificationsBot notifications; + public final PeekBot peek; public Bots(UiDevice device, UiAutomation automation, Context context, int timeout) { main = new UiBot(device, context, TIMEOUT); @@ -61,13 +62,14 @@ public final class Bots { menu = new MenuBot(device, context, TIMEOUT); inspector = new InspectorBot(device, context, TIMEOUT); notifications = new NotificationsBot(device, context, TIMEOUT); + peek = new PeekBot(device, context, TIMEOUT); } /** * A test helper class that provides support for controlling directory list * and making assertions against the state of it. */ - static abstract class BaseBot { + public static abstract class BaseBot { public final UiDevice mDevice; final Context mContext; final int mTimeout; diff --git a/tests/common/com/android/documentsui/bots/PeekBot.kt b/tests/common/com/android/documentsui/bots/PeekBot.kt new file mode 100644 index 000000000..6906d5a1a --- /dev/null +++ b/tests/common/com/android/documentsui/bots/PeekBot.kt @@ -0,0 +1,50 @@ +/* + * 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.bots + +import android.content.Context +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue + +/** + * A test helper class that provides support for controlling the peek overlay + * and making assertions against the state of it. + */ +class PeekBot( + device: UiDevice, + context: Context, + timeout: Int +) : Bots.BaseBot(device, context, timeout) { + + private val mOverlayId: String = "$mTargetPackage:id/peek_overlay" + private val mContainerId: String = "$mTargetPackage:id/peek_container" + + fun assertPeekActive() { + val peekContainer = findPeekContainer() + assertTrue(peekContainer.exists()) + } + + fun assertPeekHidden() { + val peekContainer = findPeekContainer() + assertFalse(peekContainer.exists()) + } + + fun findPeekContainer(): UiObject { + return findObject(mOverlayId, mContainerId) + } +} 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/common/com/android/documentsui/testing/TestPeekViewManager.kt b/tests/common/com/android/documentsui/testing/TestPeekViewManager.kt new file mode 100644 index 000000000..e3ad6033c --- /dev/null +++ b/tests/common/com/android/documentsui/testing/TestPeekViewManager.kt @@ -0,0 +1,34 @@ +/* + * 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 android.app.Activity +import androidx.fragment.app.FragmentManager +import com.android.documentsui.base.DocumentInfo +import com.android.documentsui.peek.PeekViewManager + +class TestPeekViewManager(mActivity: Activity) : PeekViewManager(mActivity) { + + val peekDocument = TestEventListener<DocumentInfo>() + + override fun initFragment(fm: FragmentManager) { + throw UnsupportedOperationException() + } + + override fun peekDocument(doc: DocumentInfo) { + peekDocument.accept(doc) + } +}
\ No newline at end of file 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/functional/com/android/documentsui/archives/ArchiveHandleTest.java b/tests/functional/com/android/documentsui/archives/ArchiveHandleTest.java index d8a1f4225..46b2698df 100644 --- a/tests/functional/com/android/documentsui/archives/ArchiveHandleTest.java +++ b/tests/functional/com/android/documentsui/archives/ArchiveHandleTest.java @@ -22,6 +22,8 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static java.util.Objects.requireNonNull; + import android.os.ParcelFileDescriptor; import androidx.annotation.NonNull; @@ -49,27 +51,24 @@ public class ArchiveHandleTest { @Rule public ArchiveFileTestRule mArchiveFileTestRule = new ArchiveFileTestRule(); - - private ArchiveHandle prepareArchiveHandle(String archivePath, String suffix, - String mimeType) throws IOException, CompressorException, ArchiveException { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile(archivePath, suffix); + private ArchiveHandle prepareArchiveHandle(String archivePath, String suffix, String mimeType) + throws IOException, CompressorException, ArchiveException { + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile(archivePath, + suffix); return ArchiveHandle.create(parcelFileDescriptor, mimeType); } - private static ArchiveEntry getFileInArchive(Enumeration<ArchiveEntry> enumeration, - String pathInArchive) { + private static ArchiveEntry getFileInArchive(Enumeration<ArchiveEntry> enumeration) { while (enumeration.hasMoreElements()) { ArchiveEntry entry = enumeration.nextElement(); - if (entry.getName().equals(pathInArchive)) { + if (entry.getName().equals("hello/inside_folder/hello_insside.txt")) { return entry; } } return null; } - private static class ArchiveEntryRecord implements ArchiveEntry { private final String mName; private final long mSize; @@ -91,11 +90,9 @@ public class ArchiveHandleTest { return false; } - if (obj instanceof ArchiveEntryRecord) { - ArchiveEntryRecord recordB = (ArchiveEntryRecord) obj; - return mName.equals(recordB.mName) - && mSize == recordB.mSize - && mIsDirectory == recordB.mIsDirectory; + if (obj instanceof ArchiveEntryRecord record) { + return mName.equals(record.mName) && mSize == record.mSize + && mIsDirectory == record.mIsDirectory; } return false; @@ -124,13 +121,13 @@ public class ArchiveHandleTest { @NonNull @Override public String toString() { - return String.format(Locale.ENGLISH, "name: %s, size: %d, isDirectory: %b", - mName, mSize, mIsDirectory); + return String.format(Locale.ENGLISH, "name: %s, size: %d, isDirectory: %b", mName, + mSize, mIsDirectory); } } private static List<ArchiveEntry> transformToIterable(Enumeration<ArchiveEntry> enumeration) { - List list = new ArrayList<ArchiveEntry>(); + List<ArchiveEntry> list = new ArrayList<>(); while (enumeration.hasMoreElements()) { list.add(new ArchiveEntryRecord(enumeration.nextElement())); } @@ -183,311 +180,259 @@ public class ArchiveHandleTest { } @Test - public void buildArchiveHandle_withoutFileDescriptor_shouldBeIllegal() throws Exception { - try { - ArchiveHandle.create(null, - "application/x-7z-compressed"); - fail("It should not be here!"); - } catch (NullPointerException e) { - /* do nothing */ - } - } - - @Test - public void buildArchiveHandle_withWrongMimeType_shouldBeIllegal() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/7z/hello.7z", ".7z"); - - try { - ArchiveHandle.create(parcelFileDescriptor, null); - fail("It should not be here!"); - } catch (IllegalArgumentException e) { - /* do nothing */ - } - } - - @Test public void buildArchiveHandle_sevenZFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", - ".7z", "application/x-7z-compressed"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", ".7z", + "application/x-7z-compressed")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void buildArchiveHandle_zipFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - assertThat(archiveHandle).isNotNull(); - } - - @Test - public void buildArchiveHandle_zipWithWrongMimeType_shouldBeNull() throws Exception { - try { - prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/xxxzip"); - fail("It should not be here!"); - } catch (UnsupportedOperationException e) { - /* do nothing */ + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + assertThat(archiveHandle).isNotNull(); } } @Test public void buildArchiveHandle_tarFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar/hello.tar", - ".tar", "application/x-gtar"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar/hello.tar", ".tar", + "application/x-gtar")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void buildArchiveHandle_tgzFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_gz/hello.tgz", - ".tgz", "application/x-compressed-tar"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_gz/hello.tgz", ".tgz", + "application/x-compressed-tar")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void buildArchiveHandle_tarGzFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/tar_gz/hello_tar_gz", ".tar.gz", - "application/x-compressed-tar"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_gz/hello_tar_gz", + ".tar.gz", "application/x-compressed-tar")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void buildArchiveHandle_tarBzipFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/tar_bz2/hello.tar.bz2", - ".tar.bz2", "application/x-bzip-compressed-tar"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_bz2/hello.tar.bz2", + ".tar.bz2", "application/x-bzip-compressed-tar")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void buildArchiveHandle_tarXzFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/xz/hello.tar.xz", ".tar.xz", - "application/x-xz-compressed-tar"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/xz/hello.tar.xz", + ".tar.xz", "application/x-xz-compressed-tar")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void buildArchiveHandle_tarBrFile_shouldNotNull() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/brotli/hello.tar.br", ".tar.br", - "application/x-brotli-compressed-tar"); - - assertThat(archiveHandle).isNotNull(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/brotli/hello.tar.br", + ".tar.br", "application/x-brotli-compressed-tar")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void getMimeType_sevenZFile_shouldBeSevenZ() throws CompressorException, ArchiveException, IOException { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", - ".7z", "application/x-7z-compressed"); - - assertThat(archiveHandle.getMimeType()).isEqualTo("application/x-7z-compressed"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", ".7z", + "application/x-7z-compressed")) { + assertThat(archiveHandle.getMimeType()).isEqualTo("application/x-7z-compressed"); + } } @Test public void getMimeType_tarBrotli_shouldBeBrotliCompressedTar() throws CompressorException, ArchiveException, IOException { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/brotli/hello.tar.br", ".tar.br", - "application/x-brotli-compressed-tar"); - - assertThat(archiveHandle.getMimeType()) - .isEqualTo("application/x-brotli-compressed-tar"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/brotli/hello.tar.br", + ".tar.br", "application/x-brotli-compressed-tar")) { + assertThat(archiveHandle.getMimeType()).isEqualTo( + "application/x-brotli-compressed-tar"); + } } @Test public void getMimeType_tarXz_shouldBeXzCompressedTar() throws CompressorException, ArchiveException, IOException { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/xz/hello.tar.xz", ".tar.xz", - "application/x-xz-compressed-tar"); - - assertThat(archiveHandle.getMimeType()) - .isEqualTo("application/x-xz-compressed-tar"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/xz/hello.tar.xz", + ".tar.xz", "application/x-xz-compressed-tar")) { + assertThat(archiveHandle.getMimeType()).isEqualTo("application/x-xz-compressed-tar"); + } } @Test public void getMimeType_tarGz_shouldBeCompressedTar() throws CompressorException, ArchiveException, IOException { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/tar_gz/hello_tar_gz", ".tar.gz", - "application/x-compressed-tar"); - - assertThat(archiveHandle.getMimeType()) - .isEqualTo("application/x-compressed-tar"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_gz/hello_tar_gz", + ".tar.gz", "application/x-compressed-tar")) { + assertThat(archiveHandle.getMimeType()).isEqualTo("application/x-compressed-tar"); + } } @Test public void getCommonArchive_tarBrFile_shouldBeCommonArchiveInputHandle() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/brotli/hello.tar.br", ".tar.br", - "application/x-brotli-compressed-tar"); - - assertThat(archiveHandle.toString()).contains("CommonArchiveInputHandle"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/brotli/hello.tar.br", + ".tar.br", "application/x-brotli-compressed-tar")) { + assertThat(archiveHandle.toString()).contains("CommonArchiveInputHandle"); + } } @Test public void getCommonArchive_sevenZFile_shouldBeSevenZFileHandle() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", - ".7z", "application/x-7z-compressed"); - - assertThat(archiveHandle.toString()).contains("SevenZFileHandle"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", ".7z", + "application/x-7z-compressed")) { + assertThat(archiveHandle.toString()).contains("SevenZFileHandle"); + } } - @Test public void getCommonArchive_zipFile_shouldBeZipFileHandle() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - assertThat(archiveHandle.toString()).contains("ZipFileHandle"); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + assertThat(archiveHandle.toString()).contains("ZipFileHandle"); + } } @Test public void close_zipFile_shouldBeSuccess() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - archiveHandle.close(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void close_sevenZFile_shouldBeSuccess() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", - ".7z", "application/x-7z-compressed"); - - archiveHandle.close(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/7z/hello.7z", ".7z", + "application/x-7z-compressed")) { + assertThat(archiveHandle).isNotNull(); + } } @Test public void closeInputStream_zipFile_shouldBeSuccess() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - InputStream inputStream = archiveHandle.getInputStream( - getFileInArchive(archiveHandle.getEntries(), - "hello/inside_folder/hello_insside.txt")); - - assertThat(inputStream).isNotNull(); - - inputStream.close(); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + try (InputStream inputStream = archiveHandle.getInputStream( + requireNonNull(getFileInArchive(archiveHandle.getEntries())))) { + assertThat(inputStream).isNotNull(); + } + } } @Test public void close_zipFile_shouldNotOpen() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/zip/hello.zip", ".zip"); + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile( + "archives/zip/hello.zip", ".zip"); - ArchiveHandle archiveHandle = ArchiveHandle.create(parcelFileDescriptor, - "application/zip"); + ArchiveHandle archiveHandle = ArchiveHandle.create(parcelFileDescriptor, "application/zip"); archiveHandle.close(); - FileInputStream fileInputStream = - new FileInputStream(parcelFileDescriptor.getFileDescriptor()); + FileInputStream fileInputStream = new FileInputStream( + parcelFileDescriptor.getFileDescriptor()); assertThat(fileInputStream).isNotNull(); } @Test public void getInputStream_zipFile_shouldHaveTheSameContent() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/zip/hello.zip", ".zip"); + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile( + "archives/zip/hello.zip", ".zip"); String expectedContent = mArchiveFileTestRule.getAssetText( "archives/original/hello/inside_folder/hello_insside.txt"); - ArchiveHandle archiveHandle = ArchiveHandle.create(parcelFileDescriptor, - "application/zip"); + ArchiveHandle archiveHandle = ArchiveHandle.create(parcelFileDescriptor, "application/zip"); InputStream inputStream = archiveHandle.getInputStream( - getFileInArchive(archiveHandle.getEntries(), - "hello/inside_folder/hello_insside.txt")); + requireNonNull(getFileInArchive(archiveHandle.getEntries()))); - assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)) - .isEqualTo(expectedContent); + assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)).isEqualTo( + expectedContent); } @Test public void getInputStream_zipFileNotExistEntry_shouldFail() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - ArchiveEntry archiveEntry = mock(ArchiveEntry.class); - when(archiveEntry.getName()).thenReturn("/not_exist_entry"); - - try { - archiveHandle.getInputStream(archiveEntry); - fail("It should not be here."); - } catch (ClassCastException e) { - /* do nothing */ + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + ArchiveEntry archiveEntry = mock(ArchiveEntry.class); + when(archiveEntry.getName()).thenReturn("/not_exist_entry"); + + try { + archiveHandle.getInputStream(archiveEntry); + fail("It should not be here."); + } catch (ClassCastException e) { + /* do nothing */ + } } } @Test public void getInputStream_directoryEntry_shouldFail() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - ArchiveEntry archiveEntry = mock(ArchiveEntry.class); - when(archiveEntry.isDirectory()).thenReturn(true); - - try { - archiveHandle.getInputStream(archiveEntry); - fail("It should not be here."); - } catch (IllegalArgumentException e) { - /* expected, do nothing */ + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + ArchiveEntry archiveEntry = mock(ArchiveEntry.class); + when(archiveEntry.isDirectory()).thenReturn(true); + + try { + archiveHandle.getInputStream(archiveEntry); + fail("It should not be here."); + } catch (IllegalArgumentException e) { + /* expected, do nothing */ + } } } @Test public void getInputStream_negativeSizeEntry_shouldFail() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - ArchiveEntry archiveEntry = mock(ArchiveEntry.class); - when(archiveEntry.isDirectory()).thenReturn(false); - when(archiveEntry.getSize()).thenReturn(-1L); - - try { - archiveHandle.getInputStream(archiveEntry); - fail("It should not be here."); - } catch (IllegalArgumentException e) { - /* expected, do nothing */ + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + ArchiveEntry archiveEntry = mock(ArchiveEntry.class); + when(archiveEntry.isDirectory()).thenReturn(false); + when(archiveEntry.getSize()).thenReturn(-1L); + + try { + archiveHandle.getInputStream(archiveEntry); + fail("It should not be here."); + } catch (IllegalArgumentException e) { + /* expected, do nothing */ + } } } @Test public void getInputStream_emptyStringEntry_shouldFail() throws Exception { - ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", - ".zip", "application/zip"); - - ArchiveEntry archiveEntry = mock(ArchiveEntry.class); - when(archiveEntry.isDirectory()).thenReturn(false); - when(archiveEntry.getSize()).thenReturn(14L); - when(archiveEntry.getName()).thenReturn(""); - - try { - archiveHandle.getInputStream(archiveEntry); - fail("It should not be here."); - } catch (IllegalArgumentException e) { - /* expected, do nothing */ + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + ArchiveEntry archiveEntry = mock(ArchiveEntry.class); + when(archiveEntry.isDirectory()).thenReturn(false); + when(archiveEntry.getSize()).thenReturn(14L); + when(archiveEntry.getName()).thenReturn(""); + + try { + archiveHandle.getInputStream(archiveEntry); + fail("It should not be here."); + } catch (IllegalArgumentException e) { + /* expected, do nothing */ + } } } @Test public void getInputStream_sevenZFile_shouldHaveTheSameContent() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/7z/hello.7z", ".7z"); + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile( + "archives/7z/hello.7z", ".7z"); String expectedContent = mArchiveFileTestRule.getAssetText( "archives/original/hello/inside_folder/hello_insside.txt"); @@ -496,17 +441,16 @@ public class ArchiveHandleTest { "application/x-7z-compressed"); InputStream inputStream = archiveHandle.getInputStream( - getFileInArchive(archiveHandle.getEntries(), - "hello/inside_folder/hello_insside.txt")); + requireNonNull(getFileInArchive(archiveHandle.getEntries()))); - assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)) - .isEqualTo(expectedContent); + assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)).isEqualTo( + expectedContent); } @Test public void getInputStream_tarGzFile_shouldHaveTheSameContent() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/tar_gz/hello.tgz", ".tar.gz"); + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile( + "archives/tar_gz/hello.tgz", ".tar.gz"); String expectedContent = mArchiveFileTestRule.getAssetText( "archives/original/hello/inside_folder/hello_insside.txt"); @@ -515,40 +459,16 @@ public class ArchiveHandleTest { "application/x-compressed-tar"); InputStream inputStream = archiveHandle.getInputStream( - getFileInArchive(archiveHandle.getEntries(), - "hello/inside_folder/hello_insside.txt")); - - assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)) - .isEqualTo(expectedContent); - } - - @Test - public void getInputStream_tarGzFileNullEntry_getNullInputStream() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/tar_gz/hello.tgz", ".tar.gz"); - - String expectedContent = mArchiveFileTestRule.getAssetText( - "archives/original/hello/inside_folder/hello_insside.txt"); + requireNonNull(getFileInArchive(archiveHandle.getEntries()))); - ArchiveHandle archiveHandle = ArchiveHandle.create(parcelFileDescriptor, - "application/x-compressed-tar"); - - try { - archiveHandle.getInputStream(null); - fail("It should not here"); - } catch (IllegalArgumentException | ArchiveException | CompressorException e) { - /* expected, do nothing */ - } + assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)).isEqualTo( + expectedContent); } - @Test public void getInputStream_tarGzFileInvalidEntry_getNullInputStream() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/tar_gz/hello.tgz", ".tar.gz"); - - String expectedContent = mArchiveFileTestRule.getAssetText( - "archives/original/hello/inside_folder/hello_insside.txt"); + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile( + "archives/tar_gz/hello.tgz", ".tar.gz"); ArchiveHandle archiveHandle = ArchiveHandle.create(parcelFileDescriptor, "application/x-compressed-tar"); @@ -565,8 +485,8 @@ public class ArchiveHandleTest { @Test public void getInputStream_tarBrotliFile_shouldHaveTheSameContent() throws Exception { - ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule - .openAssetFile("archives/brotli/hello.tar.br", ".tar.br"); + ParcelFileDescriptor parcelFileDescriptor = mArchiveFileTestRule.openAssetFile( + "archives/brotli/hello.tar.br", ".tar.br"); String expectedContent = mArchiveFileTestRule.getAssetText( "archives/original/hello/inside_folder/hello_insside.txt"); @@ -575,70 +495,63 @@ public class ArchiveHandleTest { "application/x-brotli-compressed-tar"); InputStream inputStream = archiveHandle.getInputStream( - getFileInArchive(archiveHandle.getEntries(), - "hello/inside_folder/hello_insside.txt")); + requireNonNull(getFileInArchive(archiveHandle.getEntries()))); - assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)) - .isEqualTo(expectedContent); + assertThat(ArchiveFileTestRule.getStringFromInputStream(inputStream)).isEqualTo( + expectedContent); } @Test public void getEntries_zipFile_shouldTheSameWithList() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/zip/hello.zip", ".zip", - "application/zip"); - - assertThat(transformToIterable(archiveHandle.getEntries())) - .containsAtLeastElementsIn(sExpectEntries); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/zip/hello.zip", ".zip", + "application/zip")) { + assertThat(transformToIterable(archiveHandle.getEntries())).containsAtLeastElementsIn( + sExpectEntries); + } } @Test public void getEntries_tarFile_shouldTheSameWithList() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/tar/hello.tar", ".tar", - "application/x-gtar"); - - assertThat(transformToIterable(archiveHandle.getEntries())) - .containsAtLeastElementsIn(sExpectEntries); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar/hello.tar", ".tar", + "application/x-gtar")) { + assertThat(transformToIterable(archiveHandle.getEntries())).containsAtLeastElementsIn( + sExpectEntries); + } } @Test public void getEntries_tgzFile_shouldTheSameWithList() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/tar_gz/hello.tgz", ".tgz", - "application/x-compressed-tar"); - - assertThat(transformToIterable(archiveHandle.getEntries())) - .containsAtLeastElementsIn(sExpectEntries); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_gz/hello.tgz", ".tgz", + "application/x-compressed-tar")) { + assertThat(transformToIterable(archiveHandle.getEntries())).containsAtLeastElementsIn( + sExpectEntries); + } } @Test public void getEntries_tarBzFile_shouldTheSameWithList() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/tar_bz2/hello.tar.bz2", ".tar.bz2", - "application/x-bzip-compressed-tar"); - - assertThat(transformToIterable(archiveHandle.getEntries())) - .containsAtLeastElementsIn(sExpectEntries); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/tar_bz2/hello.tar.bz2", + ".tar.bz2", "application/x-bzip-compressed-tar")) { + assertThat(transformToIterable(archiveHandle.getEntries())).containsAtLeastElementsIn( + sExpectEntries); + } } @Test public void getEntries_tarBrotliFile_shouldTheSameWithList() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/brotli/hello.tar.br", ".tar.br", - "application/x-brotli-compressed-tar"); - - assertThat(transformToIterable(archiveHandle.getEntries())) - .containsAtLeastElementsIn(sExpectEntries); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/brotli/hello.tar.br", + ".tar.br", "application/x-brotli-compressed-tar")) { + assertThat(transformToIterable(archiveHandle.getEntries())).containsAtLeastElementsIn( + sExpectEntries); + } } @Test public void getEntries_tarXzFile_shouldTheSameWithList() throws Exception { - ArchiveHandle archiveHandle = - prepareArchiveHandle("archives/xz/hello.tar.xz", ".tar.xz", - "application/x-xz-compressed-tar"); - - assertThat(transformToIterable(archiveHandle.getEntries())) - .containsAtLeastElementsIn(sExpectEntries); + try (ArchiveHandle archiveHandle = prepareArchiveHandle("archives/xz/hello.tar.xz", + ".tar.xz", "application/x-xz-compressed-tar")) { + assertThat(transformToIterable(archiveHandle.getEntries())).containsAtLeastElementsIn( + sExpectEntries); + } } } diff --git a/tests/functional/com/android/documentsui/peek/PeekUiTest.kt b/tests/functional/com/android/documentsui/peek/PeekUiTest.kt new file mode 100644 index 000000000..a7624df2f --- /dev/null +++ b/tests/functional/com/android/documentsui/peek/PeekUiTest.kt @@ -0,0 +1,68 @@ +/* + * 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.peek + +import android.os.RemoteException +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.android.documentsui.ActivityTestJunit4 +import com.android.documentsui.files.FilesActivity +import com.android.documentsui.flags.Flags +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@LargeTest +@RunWith(AndroidJUnit4::class) +@RequiresFlagsEnabled(Flags.FLAG_USE_MATERIAL3, Flags.FLAG_USE_PEEK_PREVIEW_RO) +class PeekUiTest : ActivityTestJunit4<FilesActivity?>() { + @get:Rule + val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + initTestFiles() + } + + @After + @Throws(Exception::class) + override fun tearDown() { + super.tearDown() + } + + @Throws(RemoteException::class) + override fun initTestFiles() { + mDocsHelper!!.createDocument(rootDir0, "image/png", "image.png") + } + + @Test + @Throws( + Exception::class + ) + fun testShowPeek() { + bots!!.peek.assertPeekHidden() + bots!!.directory.selectDocument("image.png") + bots!!.main.clickActionItem("Get info") + bots!!.peek.assertPeekActive() + } +} 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) diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java index 1d6ef1fd6..5b19fcdc2 100644 --- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java @@ -76,6 +76,7 @@ import com.android.documentsui.testing.TestDocumentClipper; import com.android.documentsui.testing.TestDragAndDropManager; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestFeatures; +import com.android.documentsui.testing.TestPeekViewManager; import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.UserManagers; import com.android.documentsui.ui.TestDialogController; @@ -110,6 +111,7 @@ public class ActionHandlerTest { private ActionHandler<TestActivity> mHandler; private TestDocumentClipper mClipper; private TestDragAndDropManager mDragAndDropManager; + private TestPeekViewManager mPeekViewManager; private TestFeatures mFeatures; private TestConfigStore mTestConfigStore; private boolean refreshAnswer = false; @@ -141,6 +143,7 @@ public class ActionHandlerTest { mDialogs = new TestDialogController(); mClipper = new TestDocumentClipper(); mDragAndDropManager = new TestDragAndDropManager(); + mPeekViewManager = new TestPeekViewManager(mActivity); mTestConfigStore = new TestConfigStore(); mEnv.state.configStore = mTestConfigStore; @@ -744,6 +747,8 @@ public class ActionHandlerTest { mHandler.showPreview(TestEnv.FILE_GIF); // The inspector activity is not called. mActivity.startActivity.assertNotCalled(); + mPeekViewManager.getPeekDocument().assertCalled(); + mPeekViewManager.getPeekDocument().assertLastArgument(TestEnv.FILE_GIF); } @Test @@ -751,6 +756,7 @@ public class ActionHandlerTest { public void testShowInspector() throws Exception { mHandler.showPreview(TestEnv.FILE_GIF); + mPeekViewManager.getPeekDocument().assertNotCalled(); mActivity.startActivity.assertCalled(); Intent intent = mActivity.startActivity.getLastValue(); assertTargetsComponent(intent, InspectorActivity.class); @@ -864,6 +870,7 @@ public class ActionHandlerTest { mClipper, null, // clip storage, not utilized unless we venture into *jumbo* clip territory. mDragAndDropManager, + mPeekViewManager, mEnv.injector); } } |