diff options
| author | 2024-04-22 19:37:05 +0000 | |
|---|---|---|
| committer | 2024-04-22 19:37:05 +0000 | |
| commit | 77631d7512e65c4d659367ce0b4f8f433971c0ad (patch) | |
| tree | d1c2cc93251db682defdf49c244e34846ac6970e /java/src | |
| parent | a5cf76634fa86cdfcbf3058c7ba4e80ebec471ff (diff) | |
| parent | 35e74a7706a9e6eec33d3a063ab31f19ebbe73a9 (diff) | |
Merge "Payload selection change UI improvements." into main
Diffstat (limited to 'java/src')
9 files changed, 153 insertions, 51 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 43d28761..b712788c 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -84,6 +84,7 @@ import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TabHost; +import android.widget.TabWidget; import android.widget.TextView; import android.widget.Toast; @@ -218,9 +219,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private boolean mRegistered; private PackageMonitor mPersonalPackageMonitor; private PackageMonitor mWorkPackageMonitor; - protected View mProfileView; protected ResolverDrawerLayout mResolverDrawerLayout; + private TabHost mTabHost; + private ResolverViewPager mViewPager; protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; protected final LatencyTracker mLatencyTracker = getLatencyTracker(); @@ -305,8 +307,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate = new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout); - private final View mContentView = null; - private final Map<Integer, ProfileRecord> mProfileRecords = new HashMap<>(); private boolean mExcludeSharedText = false; @@ -345,6 +345,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mChooserHelper.setInitializer(this::initialize); if (mChooserServiceFeatureFlags.chooserPayloadToggling()) { mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged); + mChooserHelper.setOnPendingSelection(this::onPendingSelection); } } @@ -406,9 +407,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override protected final void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - if (viewPager != null) { - outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); + if (mViewPager != null) { + outState.putInt(LAST_SHOWN_TAB_KEY, mViewPager.getCurrentItem()); } } @@ -648,7 +648,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void onChooserRequestChanged(ChooserRequest chooserRequest) { - // intentional reference comarison + // intentional reference comparison if (mRequest == chooserRequest) { return; } @@ -658,6 +658,23 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mChooserContentPreviewUi.updateModifyShareAction(); if (recreateAdapters) { recreatePagerAdapter(); + } else { + setTabsViewEnabled(true); + } + } + + private void onPendingSelection() { + setTabsViewEnabled(false); + } + + private void onAppTargetsLoaded(ResolverListAdapter listAdapter) { + if (mChooserMultiProfilePagerAdapter == null) { + return; + } + if (!isProfilePagerAdapterAttached() + && listAdapter == mChooserMultiProfilePagerAdapter.getActiveListAdapter()) { + mChooserMultiProfilePagerAdapter.setupViewPager(mViewPager); + setTabsViewEnabled(true); } } @@ -698,9 +715,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mRequest.getShareTargetFilter() ); + int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage(); if (mChooserMultiProfilePagerAdapter != null) { mChooserMultiProfilePagerAdapter.destroy(); } + // Update the pager adapter but do not attach it to the view till the targets are reloaded, + // see onChooserAppTargetsLoaded method. mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter( /* context = */ this, mProfilePagerResources, @@ -710,8 +730,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mRequest.getInitialIntents(), mMaxTargetsPerRow, mFeatureFlags); - mChooserMultiProfilePagerAdapter.setupViewPager( - requireViewById(com.android.internal.R.id.profile_pager)); + mChooserMultiProfilePagerAdapter.setCurrentPage(currentPage); if (mPersonalPackageMonitor != null) { mPersonalPackageMonitor.unregister(); } @@ -736,15 +755,25 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } postRebuildList( mChooserMultiProfilePagerAdapter.rebuildTabs( - mProfiles.getWorkProfilePresent() - || mProfiles.getPrivateProfilePresent())); + mProfiles.getWorkProfilePresent() || mProfiles.getPrivateProfilePresent())); + setTabsViewEnabled(false); + } + + private void setTabsViewEnabled(boolean isEnabled) { + TabWidget tabs = mTabHost.getTabWidget(); + if (tabs != null) { + tabs.setEnabled(isEnabled); + } + View tabContent = mTabHost.findViewById(com.android.internal.R.id.profile_pager); + if (tabContent != null) { + tabContent.setEnabled(isEnabled); + } } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { - ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - if (viewPager != null) { - viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); + if (mViewPager != null) { + mViewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); } mChooserMultiProfilePagerAdapter.clearInactiveProfileCache(); } @@ -1167,8 +1196,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements : R.layout.chooser_grid; setContentView(mLayoutId); - mChooserMultiProfilePagerAdapter.setupViewPager( - requireViewById(com.android.internal.R.id.profile_pager)); + mTabHost = findViewById(com.android.internal.R.id.profile_tabhost); + mViewPager = requireViewById(com.android.internal.R.id.profile_pager); + mChooserMultiProfilePagerAdapter.setupViewPager(mViewPager); boolean result = postRebuildList(rebuildCompleted); Trace.endSection(); return result; @@ -1235,16 +1265,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void setupProfileTabs() { - TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost); - ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - mChooserMultiProfilePagerAdapter.setupProfileTabs( getLayoutInflater(), - tabHost, - viewPager, + mTabHost, + mViewPager, R.layout.resolver_profile_tab_button, com.android.internal.R.id.profile_pager, - () -> onProfileTabSelected(viewPager.getCurrentItem()), + () -> onProfileTabSelected(mViewPager.getCurrentItem()), new OnProfileSelectedListener() { @Override public void onProfilePageSelected(@ProfileType int profileId, int pageNumber) {} @@ -1255,8 +1282,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } }); mOnSwitchOnWorkSelectedListener = () -> { - View workTab = tabHost.getTabWidget().getChildAt( - mChooserMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK)); + View workTab = mTabHost.getTabWidget().getChildAt( + mChooserMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK)); workTab.setFocusable(true); workTab.setFocusableInTouchMode(true); workTab.requestFocus(); @@ -1486,9 +1513,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, mSystemWindowInsets.right, 0); } - ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - if (viewPager.isLayoutRtl()) { - mChooserMultiProfilePagerAdapter.setupViewPager(viewPager); + if (mViewPager.isLayoutRtl()) { + mChooserMultiProfilePagerAdapter.setupViewPager(mViewPager); } mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); @@ -2151,7 +2177,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements */ private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (mChooserMultiProfilePagerAdapter == null) { + if (mChooserMultiProfilePagerAdapter == null || !isProfilePagerAdapterAttached()) { return; } RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); @@ -2268,6 +2294,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return Math.min(offset, bottom - top); } + private boolean isProfilePagerAdapterAttached() { + return mChooserMultiProfilePagerAdapter == mViewPager.getAdapter(); + } + /** * If we have a tabbed view and are showing 1 row in the current profile and an empty * state screen in another profile, to prevent cropping of the empty state screen we show @@ -2295,9 +2325,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements //TODO: move this block inside ChooserListAdapter (should be called when // ResolverListAdapter#mPostListReadyRunnable is executed. if (chooserListAdapter.getDisplayResolveInfoCount() == 0) { + if (rebuildComplete && mChooserServiceFeatureFlags.chooserPayloadToggling()) { + onAppTargetsLoaded(listAdapter); + } chooserListAdapter.notifyDataSetChanged(); } else { - chooserListAdapter.updateAlphabeticalList(); + if (mChooserServiceFeatureFlags.chooserPayloadToggling()) { + chooserListAdapter.updateAlphabeticalList( + () -> onAppTargetsLoaded(listAdapter)); + } else { + chooserListAdapter.updateAlphabeticalList(); + } } if (rebuildComplete) { @@ -2541,8 +2579,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void setHorizontalScrollingEnabled(boolean enabled) { - ResolverViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - viewPager.setSwipingEnabled(enabled); + mViewPager.setSwipingEnabled(enabled); } private void setVerticalScrollEnabled(boolean enabled) { diff --git a/java/src/com/android/intentresolver/ChooserHelper.kt b/java/src/com/android/intentresolver/ChooserHelper.kt index 25c2b40f..6317ee1d 100644 --- a/java/src/com/android/intentresolver/ChooserHelper.kt +++ b/java/src/com/android/intentresolver/ChooserHelper.kt @@ -28,9 +28,8 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.intentresolver.annotation.JavaInterop import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ActivityResultRepository +import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PendingSelectionCallbackRepository import com.android.intentresolver.data.model.ChooserRequest -import com.android.intentresolver.domain.interactor.UserInteractor -import com.android.intentresolver.inject.Background import com.android.intentresolver.ui.viewmodel.ChooserViewModel import com.android.intentresolver.validation.Invalid import com.android.intentresolver.validation.Valid @@ -38,9 +37,13 @@ import com.android.intentresolver.validation.log import dagger.hilt.android.scopes.ActivityScoped import java.util.function.Consumer import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch private const val TAG: String = "ChooserHelper" @@ -79,17 +82,19 @@ class ChooserHelper @Inject constructor( hostActivity: Activity, - private val userInteractor: UserInteractor, private val activityResultRepo: ActivityResultRepository, - @Background private val background: CoroutineDispatcher, + private val pendingSelectionCallbackRepo: PendingSelectionCallbackRepository, ) : DefaultLifecycleObserver { // This is guaranteed by Hilt, since only a ComponentActivity is injectable. private val activity: ComponentActivity = hostActivity as ComponentActivity private val viewModel by activity.viewModels<ChooserViewModel>() + // TODO: provide the following through an init object passed into [setInitialize] private lateinit var activityInitializer: Runnable - + /** Invoked when there are updates to ChooserRequest */ var onChooserRequestChanged: Consumer<ChooserRequest> = Consumer {} + /** Invoked when there are a new change to payload selection */ + var onPendingSelection: Runnable = Runnable {} init { activity.lifecycle.addObserver(this) @@ -130,8 +135,25 @@ constructor( } activity.lifecycleScope.launch { + val hasPendingCallbackFlow = + pendingSelectionCallbackRepo.pendingTargetIntent + .map { it != null } + .distinctUntilChanged() + .onEach { hasPendingCallback -> + if (hasPendingCallback) { + onPendingSelection.run() + } + } activity.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.request.collect { onChooserRequestChanged.accept(it) } + viewModel.request + .combine(hasPendingCallbackFlow) { request, hasPendingCallback -> + request to hasPendingCallback + } + // only take ChooserRequest if there are no pending callbacks + .filter { !it.second } + .map { it.first } + .distinctUntilChanged(areEquivalent = { old, new -> old === new }) + .collect { onChooserRequestChanged.accept(it) } } } } diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index e8d4fdde..29b5698b 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -347,9 +347,16 @@ public class ChooserListAdapter extends ResolverListAdapter { false); } + @Override + public void onDestroy() { + super.onDestroy(); + notifyDataSetChanged(); + } + @VisibleForTesting @Override public void onBindView(View view, TargetInfo info, int position) { + view.setEnabled(!isDestroyed()); final ViewHolder holder = (ViewHolder) view.getTag(); resetViewHolder(holder); @@ -478,7 +485,17 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + /** + * Group application targets + */ public void updateAlphabeticalList() { + updateAlphabeticalList(() -> {}); + } + + /** + * Group application targets + */ + public void updateAlphabeticalList(Runnable onCompleted) { final DisplayResolveInfoAzInfoComparator comparator = new DisplayResolveInfoAzInfoComparator(mContext); final List<DisplayResolveInfo> allTargets = new ArrayList<>(); @@ -523,6 +540,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mSortedList.clear(); mSortedList.addAll(newList); notifyDataSetChanged(); + onCompleted.run(); } private void loadMissingLabels(List<DisplayResolveInfo> targets) { diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index 80d07d2c..9843cf8d 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -448,6 +448,9 @@ public class ResolverListAdapter extends BaseAdapter { // Send an "incomplete" list-ready while the async task is running. postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false); mBgExecutor.execute(() -> { + if (isDestroyed()) { + return; + } List<ResolvedComponentInfo> sortedComponents = null; //TODO: the try-catch logic here is to formally match the AsyncTask's behavior. // Empirically, we don't need it as in the case on an exception, the app will crash and @@ -785,6 +788,10 @@ public class ResolverListAdapter extends BaseAdapter { mRequestedLabels.clear(); } + public final boolean isDestroyed() { + return mDestroyed.get(); + } + private static ColorMatrixColorFilter getSuspendedColorMatrix() { if (sSuspendedMatrixColorFilter == null) { diff --git a/java/src/com/android/intentresolver/ResolverViewPager.java b/java/src/com/android/intentresolver/ResolverViewPager.java index 0496579d..891ace87 100644 --- a/java/src/com/android/intentresolver/ResolverViewPager.java +++ b/java/src/com/android/intentresolver/ResolverViewPager.java @@ -75,6 +75,12 @@ public class ResolverViewPager extends ViewPager { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return !isLayoutRtl() && mSwipingEnabled && super.onInterceptTouchEvent(ev); + return !isEnabled() + || (!isLayoutRtl() && mSwipingEnabled && super.onInterceptTouchEvent(ev)); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return isEnabled() && super.onTouchEvent(ev); } } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ProcessTargetIntentUpdatesInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ProcessTargetIntentUpdatesInteractor.kt index 04416a3d..c202eabf 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ProcessTargetIntentUpdatesInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ProcessTargetIntentUpdatesInteractor.kt @@ -34,7 +34,7 @@ constructor( repository.pendingTargetIntent.collectLatest { targetIntent -> targetIntent ?: return@collectLatest selectionCallback.onSelectionChanged(targetIntent)?.let { update -> - chooserRequestInteractor.applyUpdate(update) + chooserRequestInteractor.applyUpdate(targetIntent, update) } repository.pendingTargetIntent.compareAndSet(targetIntent, null) } diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt index 941dfca1..dd16f0c1 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt @@ -34,9 +34,10 @@ constructor( private val repository: ChooserRequestRepository, @CustomAction private val pendingIntentSender: PendingIntentSender, ) { - fun applyUpdate(update: ShareouselUpdate) { + fun applyUpdate(targetIntent: Intent, update: ShareouselUpdate) { repository.chooserRequest.update { current -> current.copy( + targetIntent = targetIntent, callerChooserTargets = update.callerTargets.getOrDefault(current.callerChooserTargets), modifyShareAction = diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt index 429e34e9..d99d69ab 100644 --- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt +++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateTargetIntentInteractor.kt @@ -31,7 +31,7 @@ constructor( * sharing application, so that it can react to the new intent. */ fun updateTargetIntent(targetIntent: Intent) { - chooserRequestInteractor.setTargetIntent(targetIntent) repository.pendingTargetIntent.value = targetIntent + chooserRequestInteractor.setTargetIntent(targetIntent) } } diff --git a/java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java index 48de37de..11a6caca 100644 --- a/java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java @@ -302,15 +302,7 @@ public class MultiProfilePagerAdapter< viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { - mCurrentPage = position; - if (!mLoadedPages.contains(position)) { - rebuildActiveTab(true); - mLoadedPages.add(position); - } - if (mOnProfileSelectedListener != null) { - mOnProfileSelectedListener.onProfilePageSelected( - getProfileForPageNumber(position), position); - } + MultiProfilePagerAdapter.this.onPageSelected(position); } @Override @@ -325,6 +317,18 @@ public class MultiProfilePagerAdapter< mLoadedPages.add(mCurrentPage); } + private void onPageSelected(int position) { + mCurrentPage = position; + if (!mLoadedPages.contains(position)) { + rebuildActiveTab(true); + mLoadedPages.add(position); + } + if (mOnProfileSelectedListener != null) { + mOnProfileSelectedListener.onProfilePageSelected( + getProfileForPageNumber(position), position); + } + } + public void clearInactiveProfileCache() { forEachInactivePage(pageNumber -> mLoadedPages.remove(pageNumber)); } @@ -351,6 +355,13 @@ public class MultiProfilePagerAdapter< return mCurrentPage; } + /** + * Set active adapter page. A support method for the poayload reselection logic. + */ + public void setCurrentPage(int page) { + onPageSelected(page); + } + public final @ProfileType int getActiveProfile() { return getProfileForPageNumber(getCurrentPage()); } |