Loading flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -56,3 +56,11 @@ flag { bug: "411269618" is_fixed_read_only: true } flag { name: "enable_trash_flow_ro" namespace: "documentsui" description: "Enables the documents trash flow" bug: "409265240" is_fixed_read_only: true } res/flag(com.android.documentsui.flags.use_material3)/drawable/hourglass_m3.xml +2 −2 Original line number Diff line number Diff line Loading @@ -86,8 +86,8 @@ Copyright (C) 2024 The Android Open Source Project android:fillColor="?attr/colorPrimaryContainer"/> <path android:pathData="M142.88,128.29C145.03,124.54 146.11,122.66 147.42,121.86C149.31,120.71 151.69,120.71 153.58,121.86C154.89,122.66 155.97,124.54 158.12,128.29L169.77,148.59C171.9,152.31 172.97,154.17 173,155.69C173.04,157.89 171.85,159.94 169.92,161C168.59,161.73 166.44,161.73 162.14,161.73H138.86C134.56,161.73 132.41,161.73 131.08,161C129.15,159.94 127.96,157.89 128,155.69C128.03,154.17 129.1,152.31 131.23,148.59L142.88,128.29Z" android:fillColor="?attr/colorErrorContainer"/> android:fillColor="#F55E57"/> <path android:pathData="M150.66,147.94C150.26,147.94 149.91,147.79 149.61,147.51C149.32,147.22 149.16,146.88 149.13,146.48L148.81,136.91C148.78,136.37 148.95,135.91 149.32,135.52C149.68,135.13 150.13,134.93 150.66,134.93C151.2,134.93 151.65,135.13 152.02,135.52C152.38,135.91 152.55,136.37 152.52,136.91L152.18,146.48C152.16,146.89 151.99,147.23 151.69,147.51C151.4,147.79 151.06,147.94 150.66,147.94ZM150.66,153.84C150.01,153.84 149.46,153.63 149.02,153.21C148.58,152.78 148.36,152.26 148.36,151.64C148.36,151.02 148.58,150.5 149.02,150.08C149.46,149.66 150.01,149.45 150.66,149.45C151.31,149.45 151.85,149.66 152.3,150.08C152.74,150.5 152.97,151.02 152.97,151.64C152.97,152.26 152.74,152.78 152.3,153.21C151.86,153.63 151.32,153.84 150.66,153.84Z" android:fillColor="?attr/colorOnError"/> android:fillColor="?attr/colorSurface"/> </vector> src/com/android/documentsui/BaseActivity.java +11 −5 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import androidx.appcompat.widget.ActionMenuView; import androidx.appcompat.widget.Toolbar; import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import com.android.documentsui.AbstractActionHandler.CommonAddons; import com.android.documentsui.Injector.Injected; Loading @@ -70,6 +71,7 @@ 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.peek.PeekViewModel; import com.android.documentsui.prefs.LocalPreferences; import com.android.documentsui.prefs.PreferencesMonitor; import com.android.documentsui.queries.CommandInterceptor; Loading Loading @@ -438,8 +440,15 @@ public abstract class BaseActivity updateRecentsSetting(); if (isUsePeekPreviewFlagEnabled()) { mPeekViewManager = new PeekViewManager(this); mPeekViewManager.initFragment(getSupportFragmentManager(), savedInstanceState); ViewModelProvider viewModelProvider = new ViewModelProvider(this); PeekViewModel viewModel = viewModelProvider.get(PeekViewModel.class); mPeekViewManager = new PeekViewManager( viewModel, findViewById(getRes(R.id.peek_overlay)), getSupportFragmentManager()); viewModel.getOverlayActive().observe( this, mPeekViewManager); } } Loading Loading @@ -1044,9 +1053,6 @@ public abstract class BaseActivity super.onSaveInstanceState(state); state.putParcelable(Shared.EXTRA_STATE, mState); mSearchManager.onSaveInstanceState(state); if (isUsePeekPreviewFlagEnabled()) { mPeekViewManager.onSaveInstanceState(state); } } @Override Loading src/com/android/documentsui/peek/PeekFragment.kt +40 −58 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import com.android.documentsui.R import com.android.documentsui.base.DocumentInfo import com.android.documentsui.util.Material3Config.Companion.getRes Loading @@ -31,7 +33,6 @@ import java.io.FileNotFoundException class PeekFragment : Fragment() { companion object { private const val TAG = "PeekFragment" private const val PEEK_DOC_INFO = "PEEK_DOC_INFO" } // Interface for custom view components that are rendered based on a DocumentInfo. Loading @@ -41,16 +42,26 @@ class PeekFragment : Fragment() { fun clear() } // The view manager is used to handle behaviors that are not managed by the fragment itself. private lateinit var viewManager: PeekViewManager // ViewModel holding the UI state of Peek, scoped to DocumentsUI's activity. private lateinit var viewModel: PeekViewModel // Top bar view. private lateinit var toolbar: MaterialToolbar private var toolbar: MaterialToolbar? = null // Rendering view. private lateinit var previewFrame: RenderView private var docInfo: DocumentInfo? = null private var previewFrame: RenderView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(requireActivity())[PeekViewModel::class.java] viewModel.docInfo.observe( requireActivity(), Observer { docInfo -> // Executes immediately when the observer is set. updateView(docInfo) } ) } @Suppress("ktlint:standard:comment-wrapping") override fun onCreateView( Loading @@ -58,72 +69,43 @@ class PeekFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate( getRes(R.layout.peek_layout), container, /* attachToRoot= */ false ) val view = inflater.inflate(getRes(R.layout.peek_layout), container, /* attachToRoot= */ false) toolbar = view.findViewById(getRes(R.id.peek_toolbar)) toolbar!!.setNavigationOnClickListener { clearAndHide() } previewFrame = view.findViewById(getRes(R.id.peek_preview)) toolbar.setNavigationOnClickListener { clearAndHide() } return view } // Called after the fragment has been created and its previous view state has been restored. // This is where the preview is rerendered, when applicable, and additional view states can be // restored. override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) if (savedInstanceState == null) { return } val doc = savedInstanceState.getParcelable(PEEK_DOC_INFO, DocumentInfo::class.java) if (doc != null) { // Update potentially stale document info. Clear and hide the Peek overlay if an // exception is caught during the process. val savedDocInfo = viewModel.docInfo.value if (savedDocInfo != null) { try { doc.updateSelf(doc.userId.getContentResolver(context), doc.userId) updateView(doc) savedDocInfo.updateSelf( savedDocInfo.userId.getContentResolver(context), savedDocInfo.userId ) updateView(savedDocInfo) } catch (e: FileNotFoundException) { Log.e(TAG, "Stale document info: $e") clearAndHide() } } return view } override fun onSaveInstanceState(state: Bundle) { super.onSaveInstanceState(state) state.putParcelable(PEEK_DOC_INFO, docInfo) } fun setViewManager(viewManager: PeekViewManager) { this.viewManager = viewManager } fun updateView(doc: DocumentInfo) { if (!lateinitInitialized()) { Log.e(TAG, "Members have not been initialized") private fun updateView(docInfo: DocumentInfo?) { if (docInfo == null) { return } docInfo = doc toolbar.title = doc.displayName previewFrame.accept(doc) if (toolbar == null || previewFrame == null) { // `updateView` will be called again when onCreateView executes. return } private fun lateinitInitialized(): Boolean { return ::viewManager.isInitialized && ::toolbar.isInitialized && ::previewFrame.isInitialized toolbar!!.title = docInfo.displayName previewFrame!!.accept(docInfo) } private fun clearAndHide() { if (!lateinitInitialized()) { Log.e(TAG, "Some members have not been initialized") return } viewManager.setContainerVisibility(false) toolbar.title = "" previewFrame.clear() viewModel.clear() toolbar?.title = "" previewFrame?.clear() } } src/com/android/documentsui/peek/PeekViewManager.kt +23 −44 Original line number Diff line number Diff line Loading @@ -15,44 +15,35 @@ */ 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.core.view.isVisible import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.Observer import com.android.documentsui.R import com.android.documentsui.base.DocumentInfo import com.android.documentsui.util.FlagUtils.Companion.isUsePeekPreviewFlagEnabled import com.android.documentsui.util.Material3Config.Companion.getRes /** Manager that controls the Peek UI. */ open class PeekViewManager(private val mActivity: Activity) { open class PeekViewManager( private val viewModel: PeekViewModel, private val container: FrameLayout, val fm: FragmentManager ) : Observer<Boolean?> { companion object { private const val TAG = "PeekViewManager" private const val PEEK_OVERLAY_ACTIVE = "PEEK_OVERLAY_ACTIVE" } private lateinit var peekFragment: PeekFragment private lateinit var container: FrameLayout open fun initFragment(fm: FragmentManager, savedInstanceState: Bundle?) { if (!isUsePeekPreviewFlagEnabled()) { Log.e(TAG, "Attempting to create PeekViewManager while Peek disabled") return init { initialize() } val container: FrameLayout? = mActivity.findViewById(getRes(R.id.peek_overlay)) if (container == null) { Log.e(TAG, "Unable to find Peek container") return } this.container = container // Initialize Peek fragment. The fragment manager automatically handles state restoration: // the fragment might already exist. protected open fun initialize() { // The fragment manager automatically handles state restoration: the fragment might already // exist. val existingFragment = fm.findFragmentById(getRes(R.id.peek_overlay)) if (existingFragment == null) { peekFragment = PeekFragment() Loading @@ -62,37 +53,25 @@ open class PeekViewManager(private val mActivity: Activity) { } else { peekFragment = existingFragment as PeekFragment } peekFragment.setViewManager(this) // Restore Peek overlay if necessary. if (savedInstanceState != null && savedInstanceState.getBoolean(PEEK_OVERLAY_ACTIVE, false)) { setContainerVisibility(true) } // Restore visibility. setContainerVisibility(viewModel.overlayActive.value == true) } open fun peekDocument(doc: DocumentInfo) { if (!::peekFragment.isInitialized) { Log.e(TAG, "PeekFragment has not been initialized") return } peekFragment.updateView(doc) setContainerVisibility(true) /** This method is called every time viewModel.overlayActive changes its value. */ override fun onChanged(value: Boolean?) { setContainerVisibility(value ?: false) } fun onSaveInstanceState(state: Bundle) { if (!::container.isInitialized) { Log.e(TAG, "lateinit container not initialized") return } state.putBoolean(PEEK_OVERLAY_ACTIVE, container.isVisible) private fun setContainerVisibility(visible: Boolean) { container.visibility = if (visible) View.VISIBLE else View.GONE } fun setContainerVisibility(visible: Boolean) { if (!::container.isInitialized) { Log.e(TAG, "Container has not been initialized") open fun peekDocument(doc: DocumentInfo) { if (!::peekFragment.isInitialized) { Log.e(TAG, "PeekFragment has not been initialized") return } container.visibility = if (visible) View.VISIBLE else View.GONE viewModel.setDocInfoAndActivateOverlay(doc) } } Loading
flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -56,3 +56,11 @@ flag { bug: "411269618" is_fixed_read_only: true } flag { name: "enable_trash_flow_ro" namespace: "documentsui" description: "Enables the documents trash flow" bug: "409265240" is_fixed_read_only: true }
res/flag(com.android.documentsui.flags.use_material3)/drawable/hourglass_m3.xml +2 −2 Original line number Diff line number Diff line Loading @@ -86,8 +86,8 @@ Copyright (C) 2024 The Android Open Source Project android:fillColor="?attr/colorPrimaryContainer"/> <path android:pathData="M142.88,128.29C145.03,124.54 146.11,122.66 147.42,121.86C149.31,120.71 151.69,120.71 153.58,121.86C154.89,122.66 155.97,124.54 158.12,128.29L169.77,148.59C171.9,152.31 172.97,154.17 173,155.69C173.04,157.89 171.85,159.94 169.92,161C168.59,161.73 166.44,161.73 162.14,161.73H138.86C134.56,161.73 132.41,161.73 131.08,161C129.15,159.94 127.96,157.89 128,155.69C128.03,154.17 129.1,152.31 131.23,148.59L142.88,128.29Z" android:fillColor="?attr/colorErrorContainer"/> android:fillColor="#F55E57"/> <path android:pathData="M150.66,147.94C150.26,147.94 149.91,147.79 149.61,147.51C149.32,147.22 149.16,146.88 149.13,146.48L148.81,136.91C148.78,136.37 148.95,135.91 149.32,135.52C149.68,135.13 150.13,134.93 150.66,134.93C151.2,134.93 151.65,135.13 152.02,135.52C152.38,135.91 152.55,136.37 152.52,136.91L152.18,146.48C152.16,146.89 151.99,147.23 151.69,147.51C151.4,147.79 151.06,147.94 150.66,147.94ZM150.66,153.84C150.01,153.84 149.46,153.63 149.02,153.21C148.58,152.78 148.36,152.26 148.36,151.64C148.36,151.02 148.58,150.5 149.02,150.08C149.46,149.66 150.01,149.45 150.66,149.45C151.31,149.45 151.85,149.66 152.3,150.08C152.74,150.5 152.97,151.02 152.97,151.64C152.97,152.26 152.74,152.78 152.3,153.21C151.86,153.63 151.32,153.84 150.66,153.84Z" android:fillColor="?attr/colorOnError"/> android:fillColor="?attr/colorSurface"/> </vector>
src/com/android/documentsui/BaseActivity.java +11 −5 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import androidx.appcompat.widget.ActionMenuView; import androidx.appcompat.widget.Toolbar; import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import com.android.documentsui.AbstractActionHandler.CommonAddons; import com.android.documentsui.Injector.Injected; Loading @@ -70,6 +71,7 @@ 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.peek.PeekViewModel; import com.android.documentsui.prefs.LocalPreferences; import com.android.documentsui.prefs.PreferencesMonitor; import com.android.documentsui.queries.CommandInterceptor; Loading Loading @@ -438,8 +440,15 @@ public abstract class BaseActivity updateRecentsSetting(); if (isUsePeekPreviewFlagEnabled()) { mPeekViewManager = new PeekViewManager(this); mPeekViewManager.initFragment(getSupportFragmentManager(), savedInstanceState); ViewModelProvider viewModelProvider = new ViewModelProvider(this); PeekViewModel viewModel = viewModelProvider.get(PeekViewModel.class); mPeekViewManager = new PeekViewManager( viewModel, findViewById(getRes(R.id.peek_overlay)), getSupportFragmentManager()); viewModel.getOverlayActive().observe( this, mPeekViewManager); } } Loading Loading @@ -1044,9 +1053,6 @@ public abstract class BaseActivity super.onSaveInstanceState(state); state.putParcelable(Shared.EXTRA_STATE, mState); mSearchManager.onSaveInstanceState(state); if (isUsePeekPreviewFlagEnabled()) { mPeekViewManager.onSaveInstanceState(state); } } @Override Loading
src/com/android/documentsui/peek/PeekFragment.kt +40 −58 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import com.android.documentsui.R import com.android.documentsui.base.DocumentInfo import com.android.documentsui.util.Material3Config.Companion.getRes Loading @@ -31,7 +33,6 @@ import java.io.FileNotFoundException class PeekFragment : Fragment() { companion object { private const val TAG = "PeekFragment" private const val PEEK_DOC_INFO = "PEEK_DOC_INFO" } // Interface for custom view components that are rendered based on a DocumentInfo. Loading @@ -41,16 +42,26 @@ class PeekFragment : Fragment() { fun clear() } // The view manager is used to handle behaviors that are not managed by the fragment itself. private lateinit var viewManager: PeekViewManager // ViewModel holding the UI state of Peek, scoped to DocumentsUI's activity. private lateinit var viewModel: PeekViewModel // Top bar view. private lateinit var toolbar: MaterialToolbar private var toolbar: MaterialToolbar? = null // Rendering view. private lateinit var previewFrame: RenderView private var docInfo: DocumentInfo? = null private var previewFrame: RenderView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(requireActivity())[PeekViewModel::class.java] viewModel.docInfo.observe( requireActivity(), Observer { docInfo -> // Executes immediately when the observer is set. updateView(docInfo) } ) } @Suppress("ktlint:standard:comment-wrapping") override fun onCreateView( Loading @@ -58,72 +69,43 @@ class PeekFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate( getRes(R.layout.peek_layout), container, /* attachToRoot= */ false ) val view = inflater.inflate(getRes(R.layout.peek_layout), container, /* attachToRoot= */ false) toolbar = view.findViewById(getRes(R.id.peek_toolbar)) toolbar!!.setNavigationOnClickListener { clearAndHide() } previewFrame = view.findViewById(getRes(R.id.peek_preview)) toolbar.setNavigationOnClickListener { clearAndHide() } return view } // Called after the fragment has been created and its previous view state has been restored. // This is where the preview is rerendered, when applicable, and additional view states can be // restored. override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) if (savedInstanceState == null) { return } val doc = savedInstanceState.getParcelable(PEEK_DOC_INFO, DocumentInfo::class.java) if (doc != null) { // Update potentially stale document info. Clear and hide the Peek overlay if an // exception is caught during the process. val savedDocInfo = viewModel.docInfo.value if (savedDocInfo != null) { try { doc.updateSelf(doc.userId.getContentResolver(context), doc.userId) updateView(doc) savedDocInfo.updateSelf( savedDocInfo.userId.getContentResolver(context), savedDocInfo.userId ) updateView(savedDocInfo) } catch (e: FileNotFoundException) { Log.e(TAG, "Stale document info: $e") clearAndHide() } } return view } override fun onSaveInstanceState(state: Bundle) { super.onSaveInstanceState(state) state.putParcelable(PEEK_DOC_INFO, docInfo) } fun setViewManager(viewManager: PeekViewManager) { this.viewManager = viewManager } fun updateView(doc: DocumentInfo) { if (!lateinitInitialized()) { Log.e(TAG, "Members have not been initialized") private fun updateView(docInfo: DocumentInfo?) { if (docInfo == null) { return } docInfo = doc toolbar.title = doc.displayName previewFrame.accept(doc) if (toolbar == null || previewFrame == null) { // `updateView` will be called again when onCreateView executes. return } private fun lateinitInitialized(): Boolean { return ::viewManager.isInitialized && ::toolbar.isInitialized && ::previewFrame.isInitialized toolbar!!.title = docInfo.displayName previewFrame!!.accept(docInfo) } private fun clearAndHide() { if (!lateinitInitialized()) { Log.e(TAG, "Some members have not been initialized") return } viewManager.setContainerVisibility(false) toolbar.title = "" previewFrame.clear() viewModel.clear() toolbar?.title = "" previewFrame?.clear() } }
src/com/android/documentsui/peek/PeekViewManager.kt +23 −44 Original line number Diff line number Diff line Loading @@ -15,44 +15,35 @@ */ 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.core.view.isVisible import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.Observer import com.android.documentsui.R import com.android.documentsui.base.DocumentInfo import com.android.documentsui.util.FlagUtils.Companion.isUsePeekPreviewFlagEnabled import com.android.documentsui.util.Material3Config.Companion.getRes /** Manager that controls the Peek UI. */ open class PeekViewManager(private val mActivity: Activity) { open class PeekViewManager( private val viewModel: PeekViewModel, private val container: FrameLayout, val fm: FragmentManager ) : Observer<Boolean?> { companion object { private const val TAG = "PeekViewManager" private const val PEEK_OVERLAY_ACTIVE = "PEEK_OVERLAY_ACTIVE" } private lateinit var peekFragment: PeekFragment private lateinit var container: FrameLayout open fun initFragment(fm: FragmentManager, savedInstanceState: Bundle?) { if (!isUsePeekPreviewFlagEnabled()) { Log.e(TAG, "Attempting to create PeekViewManager while Peek disabled") return init { initialize() } val container: FrameLayout? = mActivity.findViewById(getRes(R.id.peek_overlay)) if (container == null) { Log.e(TAG, "Unable to find Peek container") return } this.container = container // Initialize Peek fragment. The fragment manager automatically handles state restoration: // the fragment might already exist. protected open fun initialize() { // The fragment manager automatically handles state restoration: the fragment might already // exist. val existingFragment = fm.findFragmentById(getRes(R.id.peek_overlay)) if (existingFragment == null) { peekFragment = PeekFragment() Loading @@ -62,37 +53,25 @@ open class PeekViewManager(private val mActivity: Activity) { } else { peekFragment = existingFragment as PeekFragment } peekFragment.setViewManager(this) // Restore Peek overlay if necessary. if (savedInstanceState != null && savedInstanceState.getBoolean(PEEK_OVERLAY_ACTIVE, false)) { setContainerVisibility(true) } // Restore visibility. setContainerVisibility(viewModel.overlayActive.value == true) } open fun peekDocument(doc: DocumentInfo) { if (!::peekFragment.isInitialized) { Log.e(TAG, "PeekFragment has not been initialized") return } peekFragment.updateView(doc) setContainerVisibility(true) /** This method is called every time viewModel.overlayActive changes its value. */ override fun onChanged(value: Boolean?) { setContainerVisibility(value ?: false) } fun onSaveInstanceState(state: Bundle) { if (!::container.isInitialized) { Log.e(TAG, "lateinit container not initialized") return } state.putBoolean(PEEK_OVERLAY_ACTIVE, container.isVisible) private fun setContainerVisibility(visible: Boolean) { container.visibility = if (visible) View.VISIBLE else View.GONE } fun setContainerVisibility(visible: Boolean) { if (!::container.isInitialized) { Log.e(TAG, "Container has not been initialized") open fun peekDocument(doc: DocumentInfo) { if (!::peekFragment.isInitialized) { Log.e(TAG, "PeekFragment has not been initialized") return } container.visibility = if (visible) View.VISIBLE else View.GONE viewModel.setDocInfoAndActivateOverlay(doc) } }