diff options
50 files changed, 746 insertions, 206 deletions
@@ -75,6 +75,7 @@ android_library { "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-runtime-ktx", "androidx.lifecycle_lifecycle-viewmodel-ktx", + "androidx.savedstate_savedstate-ktx", "dagger2", "jsr330", "kotlin-stdlib", diff --git a/AndroidManifest-app.xml b/AndroidManifest-app.xml index 482c9f38..ba9afe28 100644 --- a/AndroidManifest-app.xml +++ b/AndroidManifest-app.xml @@ -32,7 +32,7 @@ android:requiredForAllUsers="true" android:supportsRtl="true" tools:replace="android:appComponentFactory" - android:appComponentFactory=".IntentResolverAppComponentFactory"> + android:appComponentFactory=".dagger.InjectedAppComponentFactory"> <!-- This alias needs to be maintained until there are no more devices that could be upgrading from T QPR3. (b/283722356) --> diff --git a/java/res/layout/chooser_action_row.xml b/java/res/layout/chooser_action_row.xml index 55d6adf7..7bce113e 100644 --- a/java/res/layout/chooser_action_row.xml +++ b/java/res/layout/chooser_action_row.xml @@ -20,10 +20,9 @@ <com.android.intentresolver.widget.ScrollableActionRow android:id="@androidprv:id/chooser_action_row" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:gravity="center"/> + android:layout_gravity="center_horizontal" /> <View android:layout_width="match_parent" diff --git a/java/res/layout/chooser_action_view.xml b/java/res/layout/chooser_action_view.xml index ba9134cc..e17dce0e 100644 --- a/java/res/layout/chooser_action_view.xml +++ b/java/res/layout/chooser_action_view.xml @@ -18,7 +18,7 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" style="?android:attr/borderlessButtonStyle" android:background="@drawable/chooser_action_button_bg" - android:paddingBottom="8dp" + android:paddingVertical="15dp" android:paddingHorizontal="@dimen/chooser_edge_margin_normal_half" android:clickable="true" android:drawablePadding="6dp" diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml index 0b7e1656..3bb092c2 100644 --- a/java/res/values-am/strings.xml +++ b/java/res/values-am/strings.xml @@ -92,7 +92,7 @@ <string name="miniresolver_use_personal_browser" msgid="1428911732509069292">"የግል አሳሽ ተጠቀም"</string> <string name="miniresolver_use_work_browser" msgid="7892699758493230342">"የስራ አሳሽ ተጠቀም"</string> <string name="exclude_text" msgid="5508128757025928034">"ጽሁፍን አታካትት"</string> - <string name="include_text" msgid="642280283268536140">"ፅሁፍ ጨምር"</string> + <string name="include_text" msgid="642280283268536140">"ጽሁፍ ጨምር"</string> <string name="exclude_link" msgid="1332778255031992228">"አገናኝን አታካትት"</string> <string name="include_link" msgid="827855767220339802">"አገናኝ አካትት"</string> </resources> diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml index 1a40cb98..fc1da949 100644 --- a/java/res/values-et/strings.xml +++ b/java/res/values-et/strings.xml @@ -69,7 +69,7 @@ <string name="sharing_images_only" msgid="7762589767189955438">"{count,plural, =1{Ainult pilt}other{Ainult pildid}}"</string> <string name="sharing_videos_only" msgid="5549729252364968606">"{count,plural, =1{Ainult video}other{Ainult videod}}"</string> <string name="sharing_files_only" msgid="6603666533766964768">"{count,plural, =1{Ainult fail}other{Ainult failid}}"</string> - <string name="image_preview_a11y_description" msgid="297102643932491797">"Kujutise eelvaate pisipilt"</string> + <string name="image_preview_a11y_description" msgid="297102643932491797">"Pildi eelvaate pisipilt"</string> <string name="video_preview_a11y_description" msgid="683440858811095990">"Video eelvaate pisipilt"</string> <string name="file_preview_a11y_description" msgid="7397224827802410602">"Faili eelvaate pisipilt"</string> <string name="chooser_no_direct_share_targets" msgid="4233416657754261844">"Ei ole ühtki soovitatud inimest, kellega jagada"</string> diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml index 39b5d3d9..9eb8af81 100644 --- a/java/res/values-vi/strings.xml +++ b/java/res/values-vi/strings.xml @@ -69,8 +69,8 @@ <string name="sharing_images_only" msgid="7762589767189955438">"{count,plural, =1{Chỉ chia sẻ hình ảnh}other{Chỉ chia sẻ các hình ảnh}}"</string> <string name="sharing_videos_only" msgid="5549729252364968606">"{count,plural, =1{Chỉ chia sẻ video}other{Chỉ chia sẻ các video}}"</string> <string name="sharing_files_only" msgid="6603666533766964768">"{count,plural, =1{Chỉ chia sẻ tệp}other{Chỉ chia sẻ các tệp}}"</string> - <string name="image_preview_a11y_description" msgid="297102643932491797">"Hình thu nhỏ xem trước hình ảnh"</string> - <string name="video_preview_a11y_description" msgid="683440858811095990">"Hình thu nhỏ xem trước video"</string> + <string name="image_preview_a11y_description" msgid="297102643932491797">"Hình thu nhỏ của ảnh xem trước"</string> + <string name="video_preview_a11y_description" msgid="683440858811095990">"Hình thu nhỏ của video xem trước"</string> <string name="file_preview_a11y_description" msgid="7397224827802410602">"Hình thu nhỏ xem trước tệp"</string> <string name="chooser_no_direct_share_targets" msgid="4233416657754261844">"Không có gợi ý nào về người mà bạn có thể chia sẻ"</string> <string name="usb_device_resolve_prompt_warn" msgid="4254493957548169620">"Ứng dụng này chưa được cấp quyền ghi âm nhưng vẫn có thể ghi âm thông qua thiết bị USB này."</string> diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java index 06c7e8d7..a54e8c62 100644 --- a/java/src/com/android/intentresolver/ChooserActionFactory.java +++ b/java/src/com/android/intentresolver/ChooserActionFactory.java @@ -37,6 +37,7 @@ import android.view.View; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.contentpreview.ChooserContentPreviewUi; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.widget.ActionRow; import com.android.internal.annotations.VisibleForTesting; @@ -97,7 +98,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio private final @Nullable ChooserAction mModifyShareAction; private final Consumer<Boolean> mExcludeSharedTextAction; private final Consumer</* @Nullable */ Integer> mFinishCallback; - private final ChooserActivityLogger mLogger; + private final EventLog mLogger; /** * @param context @@ -116,7 +117,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio Context context, ChooserRequestParameters chooserRequest, ChooserIntegratedDeviceComponents integratedDeviceComponents, - ChooserActivityLogger logger, + EventLog logger, Consumer<Boolean> onUpdateSharedTextIsExcluded, Callable</* @Nullable */ View> firstVisibleImageQuery, ActionActivityStarter activityStarter, @@ -152,7 +153,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio List<ChooserAction> customActions, @Nullable ChooserAction modifyShareAction, Consumer<Boolean> onUpdateSharedTextIsExcluded, - ChooserActivityLogger logger, + EventLog logger, Consumer</* @Nullable */ Integer> finishCallback) { mContext = context; mCopyButtonRunnable = copyButtonRunnable; @@ -208,7 +209,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio mModifyShareAction, mFinishCallback, () -> { - mLogger.logActionSelected(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE); + mLogger.logActionSelected(EventLog.SELECTION_TYPE_MODIFY_SHARE); }); } @@ -232,7 +233,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio Intent targetIntent, String referrerPackageName, Consumer<Integer> finishCallback, - ChooserActivityLogger logger) { + EventLog logger) { final ClipData clipData; try { clipData = extractTextToCopy(targetIntent); @@ -248,7 +249,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio Context.CLIPBOARD_SERVICE); clipboardManager.setPrimaryClipAsPackage(clipData, referrerPackageName); - logger.logActionSelected(ChooserActivityLogger.SELECTION_TYPE_COPY); + logger.logActionSelected(EventLog.SELECTION_TYPE_COPY); finishCallback.accept(Activity.RESULT_OK); }; } @@ -327,10 +328,10 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio TargetInfo editSharingTarget, Callable</* @Nullable */ View> firstVisibleImageQuery, ActionActivityStarter activityStarter, - ChooserActivityLogger logger) { + EventLog logger) { return () -> { // Log share completion via edit. - logger.logActionSelected(ChooserActivityLogger.SELECTION_TYPE_EDIT); + logger.logActionSelected(EventLog.SELECTION_TYPE_EDIT); View firstImageView = null; try { diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 7ebcf9f9..8edbba08 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -72,6 +72,7 @@ import android.view.animation.LinearInterpolator; import android.widget.TextView; import androidx.annotation.MainThread; +import androidx.lifecycle.HasDefaultViewModelProviderFactory; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -87,22 +88,28 @@ import com.android.intentresolver.contentpreview.BasePreviewViewModel; import com.android.intentresolver.contentpreview.ChooserContentPreviewUi; import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl; import com.android.intentresolver.contentpreview.PreviewViewModel; +import com.android.intentresolver.dagger.InjectedViewModelFactory; +import com.android.intentresolver.dagger.ViewModelComponent; import com.android.intentresolver.flags.FeatureFlagRepository; import com.android.intentresolver.flags.FeatureFlagRepositoryFactory; import com.android.intentresolver.grid.ChooserGridAdapter; import com.android.intentresolver.icons.DefaultTargetDataLoader; import com.android.intentresolver.icons.TargetDataLoader; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.measurements.Tracer; import com.android.intentresolver.model.AbstractResolverComparator; import com.android.intentresolver.model.AppPredictionServiceResolverComparator; import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; import com.android.intentresolver.shortcuts.AppPredictorFactory; import com.android.intentresolver.shortcuts.ShortcutLoader; +import com.android.intentresolver.ui.ChooserViewModel; import com.android.intentresolver.widget.ImagePreviewView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import org.jetbrains.annotations.NotNull; + import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -126,7 +133,7 @@ import javax.inject.Inject; * */ public class ChooserActivity extends ResolverActivity implements - ResolverListAdapter.ResolverListCommunicator { + ResolverListAdapter.ResolverListCommunicator, HasDefaultViewModelProviderFactory { private static final String TAG = "ChooserActivity"; /** @@ -166,6 +173,11 @@ public class ChooserActivity extends ResolverActivity implements private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1; private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2; + private ViewModelProvider.Factory mViewModelFactory; + private final ViewModelComponent.Builder mViewModelComponentBuilder; + + private ChooserViewModel mViewModel; + @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = { TARGET_TYPE_DEFAULT, TARGET_TYPE_CHOOSER_TARGET, @@ -193,7 +205,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mShouldDisplayLandscape; // statsd logger wrapper - protected ChooserActivityLogger mChooserActivityLogger; + protected EventLog mEventLog; private long mChooserShownTime; protected boolean mIsSuccessfullySelected; @@ -228,15 +240,31 @@ public class ChooserActivity extends ResolverActivity implements private boolean mExcludeSharedText = false; @Inject - public ChooserActivity() {} + public ChooserActivity(ViewModelComponent.Builder builder) { + mViewModelComponentBuilder = builder; + } + + @NotNull + @Override + public final ViewModelProvider.Factory getDefaultViewModelProviderFactory() { + if (mViewModelFactory == null) { + mViewModelFactory = new InjectedViewModelFactory(mViewModelComponentBuilder, + getDefaultViewModelCreationExtras(), + getReferrer()); + } + return mViewModelFactory; + } @Override protected void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); Tracer.INSTANCE.markLaunched(); final long intentReceivedTime = System.currentTimeMillis(); mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); - getChooserActivityLogger().logSharesheetTriggered(); + getEventLog().logSharesheetTriggered(); + + mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class); mFeatureFlagRepository = createFeatureFlagRepository(); mIntegratedDeviceComponents = getIntegratedDeviceComponents(); @@ -254,7 +282,9 @@ public class ChooserActivity extends ResolverActivity implements return; } - mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class); + // Note: Uses parent ViewModelProvider.Factory because RefinementManager is not injectable + mRefinementManager = new ViewModelProvider(this, super.getDefaultViewModelProviderFactory()) + .get(ChooserRefinementManager.class); mRefinementManager.getRefinementCompletion().observe(this, completion -> { if (completion.consume()) { @@ -276,7 +306,7 @@ public class ChooserActivity extends ResolverActivity implements BasePreviewViewModel previewViewModel = new ViewModelProvider(this, createPreviewViewModelFactory()) - .get(BasePreviewViewModel.class); + .get(PreviewViewModel.class); mChooserContentPreviewUi = new ChooserContentPreviewUi( getLifecycle(), previewViewModel.createOrReuseProvider(mChooserRequest), @@ -314,7 +344,7 @@ public class ChooserActivity extends ResolverActivity implements mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; - getChooserActivityLogger().logChooserActivityShown( + getEventLog().logChooserActivityShown( isWorkProfile(), mChooserRequest.getTargetType(), systemCost); if (mResolverDrawerLayout != null) { @@ -323,7 +353,7 @@ public class ChooserActivity extends ResolverActivity implements mResolverDrawerLayout.setOnCollapsedChangedListener( isCollapsed -> { mChooserMultiProfilePagerAdapter.setIsCollapsed(isCollapsed); - getChooserActivityLogger().logSharesheetExpansionChanged(isCollapsed); + getEventLog().logSharesheetExpansionChanged(isCollapsed); }); } @@ -331,7 +361,7 @@ public class ChooserActivity extends ResolverActivity implements Log.d(TAG, "System Time Cost is " + systemCost); } - getChooserActivityLogger().logShareStarted( + getEventLog().logShareStarted( getReferrerPackageName(), mChooserRequest.getTargetType(), mChooserRequest.getCallerChooserTargets().size(), @@ -550,7 +580,7 @@ public class ChooserActivity extends ResolverActivity implements if (shouldShowStickyContentPreview() || mChooserMultiProfilePagerAdapter .getCurrentRootAdapter().getSystemRowCount() != 0) { - getChooserActivityLogger().logActionShareWithPreview( + getEventLog().logActionShareWithPreview( mChooserContentPreviewUi.getPreferredContentPreview()); } return postRebuildListInternal(rebuildCompleted); @@ -910,8 +940,8 @@ public class ChooserActivity extends ResolverActivity implements if ((currentListAdapter.getCount() > 0) && (targetInfo != null)) { switch (currentListAdapter.getPositionTargetType(which)) { case ChooserListAdapter.TARGET_SERVICE: - getChooserActivityLogger().logShareTargetSelected( - ChooserActivityLogger.SELECTION_TYPE_SERVICE, + getEventLog().logShareTargetSelected( + EventLog.SELECTION_TYPE_SERVICE, targetInfo.getResolveInfo().activityInfo.processName, which, /* directTargetAlsoRanked= */ getRankedPosition(targetInfo), @@ -924,8 +954,8 @@ public class ChooserActivity extends ResolverActivity implements return; case ChooserListAdapter.TARGET_CALLER: case ChooserListAdapter.TARGET_STANDARD: - getChooserActivityLogger().logShareTargetSelected( - ChooserActivityLogger.SELECTION_TYPE_APP, + getEventLog().logShareTargetSelected( + EventLog.SELECTION_TYPE_APP, targetInfo.getResolveInfo().activityInfo.processName, (which - currentListAdapter.getSurfacedTargetInfo().size()), /* directTargetAlsoRanked= */ -1, @@ -941,8 +971,8 @@ public class ChooserActivity extends ResolverActivity implements // they are from the alphabetical pool. // TODO: why do we log a different selection type if the -1 value already // designates the same condition? - getChooserActivityLogger().logShareTargetSelected( - ChooserActivityLogger.SELECTION_TYPE_STANDARD, + getEventLog().logShareTargetSelected( + EventLog.SELECTION_TYPE_STANDARD, targetInfo.getResolveInfo().activityInfo.processName, /* value= */ -1, /* directTargetAlsoRanked= */ -1, @@ -994,7 +1024,7 @@ public class ChooserActivity extends ResolverActivity implements if (profileRecord == null) { return; } - getChooserActivityLogger().logDirectShareTargetReceived( + getEventLog().logDirectShareTargetReceived( MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER, (int) (SystemClock.elapsedRealtime() - profileRecord.loadingStartTime)); } @@ -1128,11 +1158,11 @@ public class ChooserActivity extends ResolverActivity implements } } - protected ChooserActivityLogger getChooserActivityLogger() { - if (mChooserActivityLogger == null) { - mChooserActivityLogger = new ChooserActivityLogger(); + protected EventLog getEventLog() { + if (mEventLog == null) { + mEventLog = new EventLog(); } - return mChooserActivityLogger; + return mEventLog; } public class ChooserListController extends ResolverListController { @@ -1258,7 +1288,7 @@ public class ChooserActivity extends ResolverActivity implements targetIntent, this, context.getPackageManager(), - getChooserActivityLogger(), + getEventLog(), chooserRequest, maxTargetsPerRow, initialIntentsUserSpace, @@ -1282,7 +1312,7 @@ public class ChooserActivity extends ResolverActivity implements AbstractResolverComparator resolverComparator; if (appPredictor != null) { resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(), - getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger(), + getReferrerPackageName(), appPredictor, userHandle, getEventLog(), getIntegratedDeviceComponents().getNearbySharingComponent()); } else { resolverComparator = @@ -1291,7 +1321,7 @@ public class ChooserActivity extends ResolverActivity implements getTargetIntent(), getReferrerPackageName(), null, - getChooserActivityLogger(), + getEventLog(), getResolverRankerServiceUserHandleList(userHandle), getIntegratedDeviceComponents().getNearbySharingComponent()); } @@ -1316,7 +1346,7 @@ public class ChooserActivity extends ResolverActivity implements this, mChooserRequest, mIntegratedDeviceComponents, - getChooserActivityLogger(), + getEventLog(), (isExcluded) -> mExcludeSharedText = isExcluded, this::getFirstVisibleImgPreviewView, new ChooserActionFactory.ActionActivityStarter() { @@ -1531,7 +1561,7 @@ public class ChooserActivity extends ResolverActivity implements Log.d(TAG, "app target loading time " + duration + " ms"); } addCallerChooserTargets(); - getChooserActivityLogger().logSharesheetAppLoadComplete(); + getEventLog().logSharesheetAppLoadComplete(); maybeQueryAdditionalPostProcessingTargets(chooserListAdapter); mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET); } @@ -1578,7 +1608,7 @@ public class ChooserActivity extends ResolverActivity implements } logDirectShareTargetReceived(userHandle); sendVoiceChoicesIfNeeded(); - getChooserActivityLogger().logSharesheetDirectLoadComplete(); + getEventLog().logSharesheetDirectLoadComplete(); } private void setupScrollListener() { @@ -1884,7 +1914,7 @@ public class ChooserActivity extends ResolverActivity implements @Override protected void maybeLogProfileChange() { - getChooserActivityLogger().logSharesheetProfileChanged(); + getEventLog().logSharesheetProfileChanged(); } private static class ProfileRecord { diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index de947bd1..e6d6dbf4 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -49,6 +49,7 @@ import com.android.intentresolver.chooser.NotSelectableTargetInfo; import com.android.intentresolver.chooser.SelectableTargetInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.icons.TargetDataLoader; +import com.android.intentresolver.logging.EventLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -80,7 +81,7 @@ public class ChooserListAdapter extends ResolverListAdapter { private final ChooserRequestParameters mChooserRequest; private final int mMaxRankedTargets; - private final ChooserActivityLogger mChooserActivityLogger; + private final EventLog mEventLog; private final Set<TargetInfo> mRequestedIcons = new HashSet<>(); @@ -139,7 +140,7 @@ public class ChooserListAdapter extends ResolverListAdapter { Intent targetIntent, ResolverListCommunicator resolverListCommunicator, PackageManager packageManager, - ChooserActivityLogger chooserActivityLogger, + EventLog eventLog, ChooserRequestParameters chooserRequest, int maxRankedTargets, UserHandle initialIntentsUserSpace, @@ -165,7 +166,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mPlaceHolderTargetInfo = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context); mTargetDataLoader = targetDataLoader; createPlaceHolders(); - mChooserActivityLogger = chooserActivityLogger; + mEventLog = eventLog; mShortcutSelectionLogic = new ShortcutSelectionLogic( context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp), DeviceConfig.getBoolean( @@ -633,7 +634,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mServiceTargets.removeIf(o -> o.isPlaceHolderTargetInfo()); if (mServiceTargets.isEmpty()) { mServiceTargets.add(NotSelectableTargetInfo.newEmptyTargetInfo()); - mChooserActivityLogger.logSharesheetEmptyDirectShareRow(); + mEventLog.logSharesheetEmptyDirectShareRow(); } notifyDataSetChanged(); } diff --git a/java/src/com/android/intentresolver/IntentResolverApplication.kt b/java/src/com/android/intentresolver/IntentResolverApplication.kt index 73d15f3c..61df7fff 100644 --- a/java/src/com/android/intentresolver/IntentResolverApplication.kt +++ b/java/src/com/android/intentresolver/IntentResolverApplication.kt @@ -5,15 +5,17 @@ import com.android.intentresolver.dagger.ApplicationComponent import com.android.intentresolver.dagger.DaggerApplicationComponent /** [Application] that maintains the [ApplicationComponent]. */ -class IntentResolverApplication : Application(), ApplicationComponentOwner { +open class IntentResolverApplication : Application(), ApplicationComponentOwner { private lateinit var applicationComponent: ApplicationComponent private val pendingDaggerActions = mutableSetOf<(ApplicationComponent) -> Unit>() + open fun createApplicationComponentBuilder() = DaggerApplicationComponent.builder() + override fun onCreate() { super.onCreate() - applicationComponent = DaggerApplicationComponent.builder().application(this).build() + applicationComponent = createApplicationComponentBuilder().application(this).build() pendingDaggerActions.forEach { it.invoke(applicationComponent) } pendingDaggerActions.clear() } diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index 5057d321..252d0a41 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -27,6 +27,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERS import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.PermissionChecker.PID_UNKNOWN; import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; @@ -1160,6 +1161,12 @@ public class ResolverActivity extends FragmentActivity implements // flag set, we are now losing it. That should be a very rare case // and we can live with this. intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + + // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate + // side, which means we want to open the target app on the same side as ResolverActivity. + if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { + intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT); + } return intent; } diff --git a/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt b/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt index 59b08c2c..7c997ef7 100644 --- a/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt +++ b/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt @@ -16,15 +16,18 @@ interface ActivityBinderModule { @Binds @IntoMap @ClassKey(ChooserActivity::class) + @ActivityScope fun bindChooserActivity(activity: ChooserActivity): Activity @Binds @IntoMap @ClassKey(ResolverActivity::class) + @ActivityScope fun bindResolverActivity(activity: ResolverActivity): Activity @Binds @IntoMap @ClassKey(IntentForwarderActivity::class) + @ActivityScope fun bindIntentForwarderActivity(activity: IntentForwarderActivity): Activity } diff --git a/java/src/com/android/intentresolver/dagger/ActivityComponent.kt b/java/src/com/android/intentresolver/dagger/ActivityComponent.kt new file mode 100644 index 00000000..bf5ff761 --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/ActivityComponent.kt @@ -0,0 +1,21 @@ +package com.android.intentresolver.dagger + +import android.app.Activity +import dagger.Subcomponent +import javax.inject.Provider +import javax.inject.Scope + +@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class ActivityScope + +/** Subcomponent for injections across the life of an Activity. */ +@ActivityScope +@Subcomponent(modules = [ActivityModule::class, ActivityBinderModule::class]) +interface ActivityComponent { + + @Subcomponent.Factory + interface Factory { + fun create(): ActivityComponent + } + + fun activities(): Map<Class<*>, @JvmSuppressWildcards Provider<Activity>> +} diff --git a/java/src/com/android/intentresolver/dagger/ActivityModule.kt b/java/src/com/android/intentresolver/dagger/ActivityModule.kt index a1b7551a..f6a2229d 100644 --- a/java/src/com/android/intentresolver/dagger/ActivityModule.kt +++ b/java/src/com/android/intentresolver/dagger/ActivityModule.kt @@ -2,5 +2,5 @@ package com.android.intentresolver.dagger import dagger.Module -/** Injections for the [ActivitySubComponent] */ -@Module(includes = [ActivityBinderModule::class]) interface ActivityModule +/** Bindings provided to [@ActivityScope][ActivityScope]. */ +@Module interface ActivityModule diff --git a/java/src/com/android/intentresolver/dagger/ActivityScope.kt b/java/src/com/android/intentresolver/dagger/ActivityScope.kt deleted file mode 100644 index dc3a8bef..00000000 --- a/java/src/com/android/intentresolver/dagger/ActivityScope.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.android.intentresolver.dagger - -import javax.inject.Scope - -@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class ActivityScope diff --git a/java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt b/java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt deleted file mode 100644 index 092b9088..00000000 --- a/java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.android.intentresolver.dagger - -import android.app.Activity -import dagger.Subcomponent -import javax.inject.Provider - -/** Subcomponent for injections across the life of an Activity. */ -@ActivityScope -@Subcomponent(modules = [ActivityModule::class]) -interface ActivitySubComponent { - - @Subcomponent.Builder - interface Builder { - fun build(): ActivitySubComponent - } - - fun activities(): Map<Class<*>, @JvmSuppressWildcards Provider<Activity>> -} diff --git a/java/src/com/android/intentresolver/dagger/App.kt b/java/src/com/android/intentresolver/dagger/App.kt deleted file mode 100644 index a16272e8..00000000 --- a/java/src/com/android/intentresolver/dagger/App.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.android.intentresolver.dagger - -import javax.inject.Qualifier - -@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Qualifier annotation class App diff --git a/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt b/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt index ed337e8b..9fc57712 100644 --- a/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt +++ b/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt @@ -1,7 +1,6 @@ package com.android.intentresolver.dagger import android.app.Application -import com.android.intentresolver.IntentResolverAppComponentFactory import dagger.BindsInstance import dagger.Component import javax.inject.Singleton @@ -13,11 +12,10 @@ interface ApplicationComponent { @Component.Builder interface Builder { - @BindsInstance fun application(application: Application): Builder fun build(): ApplicationComponent } - fun inject(appComponentFactory: IntentResolverAppComponentFactory) + fun inject(appComponentFactory: InjectedAppComponentFactory) } diff --git a/java/src/com/android/intentresolver/dagger/ApplicationModule.kt b/java/src/com/android/intentresolver/dagger/ApplicationModule.kt index f9285c25..4986d7e1 100644 --- a/java/src/com/android/intentresolver/dagger/ApplicationModule.kt +++ b/java/src/com/android/intentresolver/dagger/ApplicationModule.kt @@ -2,22 +2,28 @@ package com.android.intentresolver.dagger import android.app.Application import android.content.Context +import com.android.intentresolver.dagger.qualifiers.App import dagger.Module import dagger.Provides import javax.inject.Singleton -/** Injections for the [ApplicationComponent] */ +/** + * Bindings provided to [ApplicationComponent] and children. + * + * These are all @Singleton scope, one for the duration of the process. + */ @Module( - subcomponents = [ActivitySubComponent::class], - includes = [ReceiverBinderModule::class], + subcomponents = [ActivityComponent::class, ViewModelComponent::class], + includes = [ReceiverBinderModule::class, CoroutinesModule::class], ) -abstract class ApplicationModule { +interface ApplicationModule { companion object { + + @JvmStatic @Provides @Singleton @App - fun provideApplicationContext(application: Application): Context = - application.applicationContext + fun applicationContext(app: Application): Context = app.applicationContext } } diff --git a/java/src/com/android/intentresolver/dagger/CoroutinesModule.kt b/java/src/com/android/intentresolver/dagger/CoroutinesModule.kt new file mode 100644 index 00000000..5fda2c30 --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/CoroutinesModule.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import com.android.intentresolver.dagger.qualifiers.Background +import com.android.intentresolver.dagger.qualifiers.Main +import dagger.Module +import dagger.Provides +import javax.inject.Singleton +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob + +@Module +interface CoroutinesModule { + companion object { + @JvmStatic + @Provides + @Singleton + @Main + fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate + + @JvmStatic + @Provides + @Singleton + @Main + fun mainCoroutineScope(@Main mainDispatcher: CoroutineDispatcher) = + CoroutineScope(SupervisorJob() + mainDispatcher) + + @JvmStatic + @Provides + @Singleton + @Background + fun backgroundDispatcher(): CoroutineDispatcher = Dispatchers.IO + } +} diff --git a/java/src/com/android/intentresolver/IntentResolverAppComponentFactory.kt b/java/src/com/android/intentresolver/dagger/InjectedAppComponentFactory.kt index eef49ec4..db209ef0 100644 --- a/java/src/com/android/intentresolver/IntentResolverAppComponentFactory.kt +++ b/java/src/com/android/intentresolver/dagger/InjectedAppComponentFactory.kt @@ -1,4 +1,20 @@ -package com.android.intentresolver +/* + * Copyright (C) 2023 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.intentresolver.dagger import android.app.Activity import android.app.Application @@ -6,14 +22,15 @@ import android.content.BroadcastReceiver import android.content.Intent import android.util.Log import androidx.core.app.AppComponentFactory -import com.android.intentresolver.dagger.ActivitySubComponent +import com.android.intentresolver.ApplicationComponentOwner import javax.inject.Inject import javax.inject.Provider -/** [AppComponentFactory] that performs dagger injection on Android components. */ -class IntentResolverAppComponentFactory : AppComponentFactory() { +/** Provides instances of application components, delegates construction to Dagger. */ +class InjectedAppComponentFactory : AppComponentFactory() { + + @set:Inject lateinit var activityComponentBuilder: ActivityComponent.Factory - @set:Inject lateinit var activitySubComponentBuilder: Provider<ActivitySubComponent.Builder> @set:Inject lateinit var receivers: Map<Class<*>, @JvmSuppressWildcards Provider<BroadcastReceiver>> @@ -32,7 +49,7 @@ class IntentResolverAppComponentFactory : AppComponentFactory() { intent: Intent?, ): Activity { return runCatching { - val activities = activitySubComponentBuilder.get().build().activities() + val activities = activityComponentBuilder.create().activities() instantiate(className, activities) } .onFailure { @@ -70,6 +87,6 @@ class IntentResolverAppComponentFactory : AppComponentFactory() { } companion object { - private const val TAG = "IRAppComponentFactory" + private const val TAG = "AppComponentFactory" } } diff --git a/java/src/com/android/intentresolver/dagger/InjectedViewModelFactory.kt b/java/src/com/android/intentresolver/dagger/InjectedViewModelFactory.kt new file mode 100644 index 00000000..f0906d3e --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/InjectedViewModelFactory.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import android.net.Uri +import android.os.Bundle +import androidx.lifecycle.DEFAULT_ARGS_KEY +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras +import java.io.Closeable +import javax.inject.Provider +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive + +/** Instantiates new ViewModel instances using Dagger. */ +class InjectedViewModelFactory( + private val viewModelComponentBuilder: ViewModelComponent.Builder, + creationExtras: CreationExtras, + private val referrer: Uri, +) : ViewModelProvider.Factory { + + private val defaultArgs = creationExtras[DEFAULT_ARGS_KEY] ?: Bundle() + + private fun viewModelScope(viewModelClass: Class<*>) = + CloseableCoroutineScope( + SupervisorJob() + CoroutineName(viewModelClass.simpleName) + Dispatchers.Main.immediate + ) + + private fun <T> newViewModel( + providerMap: Map<Class<*>, Provider<ViewModel>>, + modelClass: Class<T> + ): T { + val provider = + providerMap[modelClass] + ?: error( + "Unable to create an instance of $modelClass. " + + "Does the class have a binding in ViewModelComponent?" + ) + return modelClass.cast(provider.get()) + } + + override fun <T : ViewModel> create(modelClass: Class<T>): T { + val viewModelScope = viewModelScope(modelClass) + val viewModelComponent = + viewModelComponentBuilder + .coroutineScope(viewModelScope) + .intentExtras(defaultArgs) + .referrer(referrer) + .build() + val viewModel = newViewModel(viewModelComponent.viewModels(), modelClass) + viewModel.addCloseable(viewModelScope) + return viewModel + } +} + +internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope { + override val coroutineContext: CoroutineContext = context + + override fun close() { + if (isActive) { + coroutineContext.cancel() + } + } +} diff --git a/java/src/com/android/intentresolver/dagger/ViewModelBinderModule.kt b/java/src/com/android/intentresolver/dagger/ViewModelBinderModule.kt new file mode 100644 index 00000000..91ba039c --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/ViewModelBinderModule.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import androidx.lifecycle.ViewModel +import com.android.intentresolver.ui.ChooserViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +/** Defines a map of injectable ViewModel classes. */ +@Module +interface ViewModelBinderModule { + @Binds + @IntoMap + @ClassKey(ChooserViewModel::class) + @ViewModelScope + fun chooserViewModel(viewModel: ChooserViewModel): ViewModel +} diff --git a/java/src/com/android/intentresolver/dagger/ViewModelComponent.kt b/java/src/com/android/intentresolver/dagger/ViewModelComponent.kt new file mode 100644 index 00000000..3e2e2681 --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/ViewModelComponent.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import android.net.Uri +import android.os.Bundle +import com.android.intentresolver.dagger.qualifiers.Referrer +import com.android.intentresolver.dagger.qualifiers.ViewModel +import dagger.BindsInstance +import dagger.Subcomponent +import javax.inject.Provider +import javax.inject.Scope +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlinx.coroutines.CoroutineScope + +@Scope @Retention(RUNTIME) @MustBeDocumented annotation class ViewModelScope + +/** + * Provides dependencies within [ViewModelScope] within a [ViewModel]. + * + * @see InjectedViewModelFactory + */ +@ViewModelScope +@Subcomponent(modules = [ViewModelModule::class, ViewModelBinderModule::class]) +interface ViewModelComponent { + + /** + * Binds instance values from the creating Activity to make them available for injection within + * [ViewModelScope]. + */ + @Subcomponent.Builder + interface Builder { + @BindsInstance fun intentExtras(@ViewModel intentExtras: Bundle): Builder + + @BindsInstance fun referrer(@Referrer uri: Uri): Builder + + @BindsInstance fun coroutineScope(@ViewModel scope: CoroutineScope): Builder + + fun build(): ViewModelComponent + } + + fun viewModels(): Map<Class<*>, @JvmSuppressWildcards Provider<androidx.lifecycle.ViewModel>> +} diff --git a/java/src/com/android/intentresolver/dagger/ViewModelModule.kt b/java/src/com/android/intentresolver/dagger/ViewModelModule.kt new file mode 100644 index 00000000..23320311 --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/ViewModelModule.kt @@ -0,0 +1,6 @@ +package com.android.intentresolver.dagger + +import dagger.Module + +/** Provides bindings shared among components within [@ViewModelScope][ViewModelScope]. */ +@Module abstract class ViewModelModule diff --git a/java/src/com/android/intentresolver/dagger/qualifiers/Qualifiers.kt b/java/src/com/android/intentresolver/dagger/qualifiers/Qualifiers.kt new file mode 100644 index 00000000..fa50170e --- /dev/null +++ b/java/src/com/android/intentresolver/dagger/qualifiers/Qualifiers.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger.qualifiers + +import javax.inject.Qualifier + +// Note: 'qualifiers' package avoids name collisions in Dagger code. + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class App + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Activity + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class ViewModel + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Main + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Background + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Delegate + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Default + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Referrer diff --git a/java/src/com/android/intentresolver/flags/Flags.kt b/java/src/com/android/intentresolver/flags/Flags.kt index b303dd1a..2c20d341 100644 --- a/java/src/com/android/intentresolver/flags/Flags.kt +++ b/java/src/com/android/intentresolver/flags/Flags.kt @@ -23,9 +23,8 @@ import com.android.systemui.flags.UnreleasedFlag // make the flags available in the flag flipper app (see go/sysui-flags). // All flags added should be included in UnbundledChooserActivityTest.ALL_FLAGS. object Flags { - private fun releasedFlag(id: Int, name: String) = - ReleasedFlag(id, name, "systemui") + private fun releasedFlag(name: String) = ReleasedFlag(name, "systemui") - private fun unreleasedFlag(id: Int, name: String, teamfood: Boolean = false) = - UnreleasedFlag(id, name, "systemui", teamfood) + private fun unreleasedFlag(name: String, teamfood: Boolean = false) = + UnreleasedFlag(name, "systemui", teamfood) } diff --git a/java/src/com/android/intentresolver/ChooserActivityLogger.java b/java/src/com/android/intentresolver/logging/EventLog.java index 1f606f26..b30e825b 100644 --- a/java/src/com/android/intentresolver/ChooserActivityLogger.java +++ b/java/src/com/android/intentresolver/logging/EventLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.intentresolver; +package com.android.intentresolver.logging; import android.annotation.Nullable; import android.content.Intent; @@ -24,6 +24,7 @@ import android.provider.MediaStore; import android.util.HashedStringCache; import android.util.Log; +import com.android.intentresolver.ChooserActivity; import com.android.intentresolver.contentpreview.ContentPreviewType; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; @@ -39,7 +40,7 @@ import com.android.internal.util.FrameworkStatsLog; * Helper for writing Sharesheet atoms to statsd log. * @hide */ -public class ChooserActivityLogger { +public class EventLog { private static final String TAG = "ChooserActivity"; private static final boolean DEBUG = true; @@ -94,12 +95,12 @@ public class ChooserActivityLogger { private final FrameworkStatsLogger mFrameworkStatsLogger; private final MetricsLogger mMetricsLogger; - public ChooserActivityLogger() { + public EventLog() { this(new UiEventLoggerImpl(), new DefaultFrameworkStatsLogger(), new MetricsLogger()); } @VisibleForTesting - ChooserActivityLogger( + EventLog( UiEventLogger uiEventLogger, FrameworkStatsLogger frameworkLogger, MetricsLogger metricsLogger) { diff --git a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java index bc54e01e..ff2d6a0f 100644 --- a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java +++ b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java @@ -30,7 +30,7 @@ import android.os.Message; import android.os.UserHandle; import android.util.Log; -import com.android.intentresolver.ChooserActivityLogger; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.ResolvedComponentInfo; import com.android.intentresolver.ResolverActivity; import com.android.intentresolver.chooser.TargetInfo; @@ -72,7 +72,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC private static final int WATCHDOG_TIMEOUT_MILLIS = 500; private final Comparator<ResolveInfo> mAzComparator; - private ChooserActivityLogger mChooserActivityLogger; + private EventLog mEventLog; protected final Handler mHandler = new Handler(Looper.getMainLooper()) { public void handleMessage(Message msg) { @@ -94,8 +94,8 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC } mHandler.removeMessages(RANKER_SERVICE_RESULT); afterCompute(); - if (mChooserActivityLogger != null) { - mChooserActivityLogger.logSharesheetAppShareRankingTimeout(); + if (mEventLog != null) { + mEventLog.logSharesheetAppShareRankingTimeout(); } break; @@ -161,12 +161,12 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC mAfterCompute = afterCompute; } - void setChooserActivityLogger(ChooserActivityLogger chooserActivityLogger) { - mChooserActivityLogger = chooserActivityLogger; + void setEventLog(EventLog eventLog) { + mEventLog = eventLog; } - ChooserActivityLogger getChooserActivityLogger() { - return mChooserActivityLogger; + EventLog getEventLog() { + return mEventLog; } protected final void afterCompute() { diff --git a/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java b/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java index ba054731..621ae306 100644 --- a/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java +++ b/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java @@ -31,7 +31,7 @@ import android.os.Message; import android.os.UserHandle; import android.util.Log; -import com.android.intentresolver.ChooserActivityLogger; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.ResolvedComponentInfo; import com.android.intentresolver.chooser.TargetInfo; @@ -72,7 +72,7 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp String referrerPackage, AppPredictor appPredictor, UserHandle user, - ChooserActivityLogger chooserActivityLogger, + EventLog eventLog, @Nullable ComponentName promoteToFirst) { super(context, intent, Lists.newArrayList(user), promoteToFirst); mContext = context; @@ -80,7 +80,7 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp mAppPredictor = appPredictor; mUser = user; mReferrerPackage = referrerPackage; - setChooserActivityLogger(chooserActivityLogger); + setEventLog(eventLog); mComparatorModel = buildUpdatedModel(); } @@ -116,7 +116,7 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp mIntent, mReferrerPackage, () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT), - getChooserActivityLogger(), + getEventLog(), mUser, mPromoteToFirst); mComparatorModel = buildUpdatedModel(); diff --git a/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java b/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java index ebaffc36..7d473660 100644 --- a/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java +++ b/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java @@ -39,7 +39,7 @@ import android.service.resolver.ResolverRankerService; import android.service.resolver.ResolverTarget; import android.util.Log; -import com.android.intentresolver.ChooserActivityLogger; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.ResolvedComponentInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.internal.logging.MetricsLogger; @@ -102,9 +102,9 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom */ public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent, String referrerPackage, Runnable afterCompute, - ChooserActivityLogger chooserActivityLogger, UserHandle targetUserSpace, + EventLog eventLog, UserHandle targetUserSpace, ComponentName promoteToFirst) { - this(launchedFromContext, intent, referrerPackage, afterCompute, chooserActivityLogger, + this(launchedFromContext, intent, referrerPackage, afterCompute, eventLog, Lists.newArrayList(targetUserSpace), promoteToFirst); } @@ -118,7 +118,7 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom */ public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent, String referrerPackage, Runnable afterCompute, - ChooserActivityLogger chooserActivityLogger, List<UserHandle> targetUserSpaceList, + EventLog eventLog, List<UserHandle> targetUserSpaceList, @Nullable ComponentName promoteToFirst) { super(launchedFromContext, intent, targetUserSpaceList, promoteToFirst); mCollator = Collator.getInstance( @@ -139,7 +139,7 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom mAction = intent.getAction(); mRankerServiceName = new ComponentName(mContext, this.getClass()); setCallBack(afterCompute); - setChooserActivityLogger(chooserActivityLogger); + setEventLog(eventLog); mComparatorModel = buildUpdatedModel(); } diff --git a/java/src/com/android/intentresolver/ui/ChooserViewModel.kt b/java/src/com/android/intentresolver/ui/ChooserViewModel.kt new file mode 100644 index 00000000..817f0b6c --- /dev/null +++ b/java/src/com/android/intentresolver/ui/ChooserViewModel.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.intentresolver.ui + +import android.util.Log +import androidx.lifecycle.ViewModel +import com.android.intentresolver.dagger.qualifiers.ViewModel as ViewModelQualifier +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.launch + +const val TAG = "ChooserViewModel" + +/** The primary container for ViewModelScope dependencies. */ +class ChooserViewModel +@Inject +constructor( + @ViewModelQualifier val viewModelScope: CoroutineScope, +) : ViewModel()
\ No newline at end of file diff --git a/java/tests/Android.bp b/java/tests/Android.bp index c381d0a8..bb287eb2 100644 --- a/java/tests/Android.bp +++ b/java/tests/Android.bp @@ -19,18 +19,21 @@ android_test { static_libs: [ "IntentResolver-core", - "androidx.test.rules", + "androidx.test.core", "androidx.test.ext.junit", + "androidx.test.ext.truth", "androidx.test.espresso.contrib", - "mockito-target-minus-junit4", "androidx.test.espresso.core", + "androidx.test.rules", "androidx.lifecycle_lifecycle-common-java8", "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-runtime-ktx", - "truth-prebuilt", - "testables", "kotlinx_coroutines_test", + "mockito-target-minus-junit4", + "testables", + "truth-prebuilt", ], + plugins: ["dagger2-compiler"], test_suites: ["general-tests"], sdk_version: "core_platform", compile_multilib: "both", diff --git a/java/tests/AndroidManifest.xml b/java/tests/AndroidManifest.xml index b397db5f..9f8dd41c 100644 --- a/java/tests/AndroidManifest.xml +++ b/java/tests/AndroidManifest.xml @@ -27,8 +27,9 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <application - tools:replace="android:name" - android:name="com.android.intentresolver.TestApplication" > + android:name="com.android.intentresolver.TestApplication" + tools:replace="android:appComponentFactory" + android:appComponentFactory="com.android.intentresolver.dagger.InjectedAppComponentFactory"> <uses-library android:name="android.test.runner" /> <activity android:name="com.android.intentresolver.ChooserWrapperActivity" /> <activity android:name="com.android.intentresolver.ResolverWrapperActivity" /> diff --git a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt index 8d994f08..af6e5f16 100644 --- a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt +++ b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt @@ -27,6 +27,7 @@ import android.graphics.drawable.Icon import android.service.chooser.ChooserAction import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.android.intentresolver.logging.EventLog import com.google.common.collect.ImmutableList import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch @@ -43,7 +44,7 @@ import org.mockito.Mockito class ChooserActionFactoryTest { private val context = InstrumentationRegistry.getInstrumentation().getContext() - private val logger = mock<ChooserActivityLogger>() + private val logger = mock<EventLog>() private val actionLabel = "Action label" private val modifyShareLabel = "Modify share" private val testAction = "com.android.intentresolver.testaction" @@ -107,7 +108,7 @@ class ChooserActionFactoryTest { action.onClicked.run() Mockito.verify(logger) - .logActionSelected(eq(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE)) + .logActionSelected(eq(EventLog.SELECTION_TYPE_MODIFY_SHARE)) assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) // Verify the pending intent has been called countdown.await(500, TimeUnit.MILLISECONDS) diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java index ce96ef63..84f5124c 100644 --- a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java +++ b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java @@ -30,6 +30,7 @@ import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileI import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.flags.FeatureFlagRepository; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.shortcuts.ShortcutLoader; import java.util.function.Consumer; @@ -64,7 +65,7 @@ public class ChooserActivityOverrideData { public Cursor resolverCursor; public boolean resolverForceException; public ImageLoader imageLoader; - public ChooserActivityLogger chooserActivityLogger; + public EventLog mEventLog; public int alternateProfileSetting; public Resources resources; public UserHandle workProfileUserHandle; @@ -87,7 +88,7 @@ public class ChooserActivityOverrideData { resolverForceException = false; resolverListController = mock(ChooserActivity.ChooserListController.class); workResolverListController = mock(ChooserActivity.ChooserListController.class); - chooserActivityLogger = mock(ChooserActivityLogger.class); + mEventLog = mock(EventLog.class); alternateProfileSetting = 0; resources = null; workProfileUserHandle = null; diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt index 4612b430..c8cb4b9b 100644 --- a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt +++ b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt @@ -31,6 +31,7 @@ import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.chooser.SelectableTargetInfo import com.android.intentresolver.chooser.TargetInfo import com.android.intentresolver.icons.TargetDataLoader +import com.android.intentresolver.logging.EventLog import com.android.internal.R import org.junit.Before import org.junit.Test @@ -49,7 +50,7 @@ class ChooserListAdapterTest { } private val context = InstrumentationRegistry.getInstrumentation().context private val resolverListController = mock<ResolverListController>() - private val chooserActivityLogger = mock<ChooserActivityLogger>() + private val mEventLog = mock<EventLog>() private val mTargetDataLoader = mock<TargetDataLoader>() private val testSubject by lazy { @@ -64,7 +65,7 @@ class ChooserListAdapterTest { Intent(), mock(), packageManager, - chooserActivityLogger, + mEventLog, mock(), 0, null, diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java index 6ac6b6d3..49305a6c 100644 --- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -37,15 +37,19 @@ import androidx.lifecycle.ViewModelProvider; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.dagger.TestViewModelComponent; import com.android.intentresolver.flags.FeatureFlagRepository; import com.android.intentresolver.grid.ChooserGridAdapter; import com.android.intentresolver.icons.TargetDataLoader; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.shortcuts.ShortcutLoader; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.List; import java.util.function.Consumer; +import javax.inject.Inject; + /** * Simple wrapper around chooser activity to be able to initiate it under test. For more * information, see {@code com.android.internal.app.ChooserWrapperActivity}. @@ -55,6 +59,11 @@ public class ChooserWrapperActivity static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance(); private UsageStatsManager mUsm; + @Inject + public ChooserWrapperActivity(TestViewModelComponent.Builder builder) { + super(builder); + } + // ResolverActivity (the base class of ChooserActivity) inspects the launched-from UID at // onCreate and needs to see some non-negative value in the test. @Override @@ -89,7 +98,7 @@ public class ChooserWrapperActivity targetIntent, this, packageManager, - getChooserActivityLogger(), + getEventLog(), chooserRequest, maxTargetsPerRow, userHandle, @@ -205,8 +214,8 @@ public class ChooserWrapperActivity } @Override - public ChooserActivityLogger getChooserActivityLogger() { - return sOverrides.chooserActivityLogger; + public EventLog getEventLog() { + return sOverrides.mEventLog; } @Override diff --git a/java/tests/src/com/android/intentresolver/IChooserWrapper.java b/java/tests/src/com/android/intentresolver/IChooserWrapper.java index af897a47..3326d7f2 100644 --- a/java/tests/src/com/android/intentresolver/IChooserWrapper.java +++ b/java/tests/src/com/android/intentresolver/IChooserWrapper.java @@ -23,6 +23,7 @@ import android.content.pm.ResolveInfo; import android.os.UserHandle; import com.android.intentresolver.chooser.DisplayResolveInfo; +import com.android.intentresolver.logging.EventLog; import java.util.concurrent.Executor; @@ -41,6 +42,6 @@ public interface IChooserWrapper { CharSequence pLabel, CharSequence pInfo, Intent replacementIntent, @Nullable TargetPresentationGetter resolveInfoPresentationGetter); UserHandle getCurrentUserHandle(); - ChooserActivityLogger getChooserActivityLogger(); + EventLog getEventLog(); Executor getMainExecutor(); } diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java index 401ede26..11e7dffd 100644 --- a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java @@ -44,6 +44,8 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import javax.inject.Inject; + /* * Simple wrapper around chooser activity to be able to initiate it under test */ @@ -53,6 +55,7 @@ public class ResolverWrapperActivity extends ResolverActivity { private final CountingIdlingResource mLabelIdlingResource = new CountingIdlingResource("LoadLabelTask"); + @Inject public ResolverWrapperActivity() { super(/* isIntentPicker= */ true); } diff --git a/java/tests/src/com/android/intentresolver/TestApplication.kt b/java/tests/src/com/android/intentresolver/TestApplication.kt index f0761fbd..4f5aefb9 100644 --- a/java/tests/src/com/android/intentresolver/TestApplication.kt +++ b/java/tests/src/com/android/intentresolver/TestApplication.kt @@ -16,32 +16,12 @@ package com.android.intentresolver -import android.app.Application import android.content.Context import android.os.UserHandle -import com.android.intentresolver.dagger.ApplicationComponent -import com.android.intentresolver.dagger.DaggerApplicationComponent +import com.android.intentresolver.dagger.DaggerTestApplicationComponent -class TestApplication : Application(), ApplicationComponentOwner { - - private lateinit var applicationComponent: ApplicationComponent - - private val pendingDaggerActions = mutableSetOf<(ApplicationComponent) -> Unit>() - - override fun onCreate() { - super.onCreate() - applicationComponent = DaggerApplicationComponent.builder().application(this).build() - pendingDaggerActions.forEach { it.invoke(applicationComponent) } - pendingDaggerActions.clear() - } - - override fun doWhenApplicationComponentReady(action: (ApplicationComponent) -> Unit) { - if (this::applicationComponent.isInitialized) { - action.invoke(applicationComponent) - } else { - pendingDaggerActions.add(action) - } - } +class TestApplication : IntentResolverApplication() { + override fun createApplicationComponentBuilder() = DaggerTestApplicationComponent.builder() // return the current context as a work profile doesn't really exist in these tests override fun createContextAsUser(user: UserHandle, flags: Int): Context = this diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java index 8233f0db..28a45051 100644 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java @@ -108,6 +108,7 @@ import androidx.test.rule.ActivityTestRule; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.contentpreview.ImageLoader; +import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.shortcuts.ShortcutLoader; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -863,7 +864,7 @@ public class UnbundledChooserActivityTest { } @Test - public void copyTextToClipboard() throws Exception { + public void copyTextToClipboard() { Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); @@ -878,7 +879,8 @@ public class UnbundledChooserActivityTest { ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( Context.CLIPBOARD_SERVICE); ClipData clipData = clipboard.getPrimaryClip(); - assertThat("testing intent sending", is(clipData.getItemAt(0).getText())); + assertThat(clipData).isNotNull(); + assertThat(clipData.getItemAt(0).getText()).isEqualTo("testing intent sending"); ClipDescription clipDescription = clipData.getDescription(); assertThat("text/plain", is(clipDescription.getMimeType(0))); @@ -900,8 +902,8 @@ public class UnbundledChooserActivityTest { onView(withId(R.id.copy)).check(matches(isDisplayed())); onView(withId(R.id.copy)).perform(click()); - ChooserActivityLogger logger = activity.getChooserActivityLogger(); - verify(logger, times(1)).logActionSelected(eq(ChooserActivityLogger.SELECTION_TYPE_COPY)); + EventLog logger = activity.getEventLog(); + verify(logger, times(1)).logActionSelected(eq(EventLog.SELECTION_TYPE_COPY)); } @Test @@ -1100,7 +1102,7 @@ public class UnbundledChooserActivityTest { final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); - ChooserActivityLogger logger = activity.getChooserActivityLogger(); + EventLog logger = activity.getEventLog(); waitForIdle(); verify(logger).logChooserActivityShown(eq(false), eq(TEST_MIME_TYPE), anyLong()); @@ -1115,7 +1117,7 @@ public class UnbundledChooserActivityTest { final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); - ChooserActivityLogger logger = activity.getChooserActivityLogger(); + EventLog logger = activity.getEventLog(); waitForIdle(); verify(logger).logChooserActivityShown(eq(true), eq(TEST_MIME_TYPE), anyLong()); @@ -1128,7 +1130,7 @@ public class UnbundledChooserActivityTest { final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity( Intent.createChooser(sendIntent, "empty preview logger test")); - ChooserActivityLogger logger = activity.getChooserActivityLogger(); + EventLog logger = activity.getEventLog(); waitForIdle(); verify(logger).logChooserActivityShown(eq(false), eq(null), anyLong()); @@ -1147,7 +1149,7 @@ public class UnbundledChooserActivityTest { waitForIdle(); // Second invocation is from onCreate - ChooserActivityLogger logger = activity.getChooserActivityLogger(); + EventLog logger = activity.getEventLog(); Mockito.verify(logger, times(1)).logActionShareWithPreview(eq(CONTENT_PREVIEW_TEXT)); } @@ -1169,7 +1171,7 @@ public class UnbundledChooserActivityTest { final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - ChooserActivityLogger logger = activity.getChooserActivityLogger(); + EventLog logger = activity.getEventLog(); Mockito.verify(logger, times(1)).logActionShareWithPreview(eq(CONTENT_PREVIEW_IMAGE)); } @@ -1371,8 +1373,8 @@ public class UnbundledChooserActivityTest { ArgumentCaptor<HashedStringCache.HashResult> hashCaptor = ArgumentCaptor.forClass(HashedStringCache.HashResult.class); - verify(activity.getChooserActivityLogger(), times(1)).logShareTargetSelected( - eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE), + verify(activity.getEventLog(), times(1)).logShareTargetSelected( + eq(EventLog.SELECTION_TYPE_SERVICE), /* packageName= */ any(), /* positionPicked= */ anyInt(), /* directTargetAlsoRanked= */ eq(-1), @@ -1452,8 +1454,8 @@ public class UnbundledChooserActivityTest { .perform(click()); waitForIdle(); - verify(activity.getChooserActivityLogger(), times(1)).logShareTargetSelected( - eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE), + verify(activity.getEventLog(), times(1)).logShareTargetSelected( + eq(EventLog.SELECTION_TYPE_SERVICE), /* packageName= */ any(), /* positionPicked= */ anyInt(), /* directTargetAlsoRanked= */ eq(0), @@ -1862,9 +1864,9 @@ public class UnbundledChooserActivityTest { .perform(click()); waitForIdle(); - ChooserActivityLogger logger = wrapper.getChooserActivityLogger(); + EventLog logger = wrapper.getEventLog(); verify(logger, times(1)).logShareTargetSelected( - eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE), + eq(EventLog.SELECTION_TYPE_SERVICE), /* packageName= */ any(), /* positionPicked= */ anyInt(), // The packages sholdn't match for app target and direct target: @@ -2194,10 +2196,10 @@ public class UnbundledChooserActivityTest { .perform(click()); waitForIdle(); - ChooserActivityLogger logger = activity.getChooserActivityLogger(); + EventLog logger = activity.getEventLog(); ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class); verify(logger, times(1)).logShareTargetSelected( - eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE), + eq(EventLog.SELECTION_TYPE_SERVICE), /* packageName= */ any(), /* positionPicked= */ anyInt(), /* directTargetAlsoRanked= */ anyInt(), diff --git a/java/tests/src/com/android/intentresolver/dagger/TestActivityBinderModule.kt b/java/tests/src/com/android/intentresolver/dagger/TestActivityBinderModule.kt new file mode 100644 index 00000000..c08bc3b2 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/dagger/TestActivityBinderModule.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import android.app.Activity +import com.android.intentresolver.ChooserWrapperActivity +import com.android.intentresolver.ResolverWrapperActivity +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface TestActivityBinderModule { + @Binds + @IntoMap + @ClassKey(ResolverWrapperActivity::class) + @ActivityScope + fun resolverWrapperActivity(activity: ResolverWrapperActivity): Activity + + @Binds + @IntoMap + @ClassKey(ChooserWrapperActivity::class) + @ActivityScope + fun chooserWrapperActivity(activity: ChooserWrapperActivity): Activity +} diff --git a/java/tests/src/com/android/intentresolver/dagger/TestActivityComponent.kt b/java/tests/src/com/android/intentresolver/dagger/TestActivityComponent.kt new file mode 100644 index 00000000..4416c852 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/dagger/TestActivityComponent.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import dagger.Subcomponent + +@ActivityScope +@Subcomponent( + modules = [ActivityModule::class, ActivityBinderModule::class, TestActivityBinderModule::class] +) +interface TestActivityComponent : ActivityComponent { + @Subcomponent.Factory + interface Factory : ActivityComponent.Factory { + override fun create(): TestActivityComponent + } +} diff --git a/java/tests/src/com/android/intentresolver/dagger/TestApplicationComponent.kt b/java/tests/src/com/android/intentresolver/dagger/TestApplicationComponent.kt new file mode 100644 index 00000000..224c46c6 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/dagger/TestApplicationComponent.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import android.app.Application +import dagger.BindsInstance +import dagger.Component +import javax.inject.Singleton + +@Singleton +@Component(modules = [TestApplicationModule::class]) +interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder { + @BindsInstance + override fun application(application: Application): TestApplicationComponent.Builder + + override fun build(): TestApplicationComponent + } +} diff --git a/java/tests/src/com/android/intentresolver/dagger/TestApplicationModule.kt b/java/tests/src/com/android/intentresolver/dagger/TestApplicationModule.kt new file mode 100644 index 00000000..714748d2 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/dagger/TestApplicationModule.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import dagger.Binds +import dagger.Module +import javax.inject.Singleton + +@Module( + subcomponents = [TestActivityComponent::class, TestViewModelComponent::class], + includes = [ReceiverBinderModule::class, CoroutinesModule::class] +) +interface TestApplicationModule : ApplicationModule { + + /** Required to support field injection of [InjectedAppComponentFactory] */ + @Binds + @Singleton + fun activityComponent(component: TestActivityComponent.Factory): ActivityComponent.Factory + + /** Required to support injection into Activity instances */ + @Binds + @Singleton + fun viewModelComponent(component: TestViewModelComponent.Builder): ViewModelComponent.Builder +} diff --git a/java/tests/src/com/android/intentresolver/dagger/TestViewModelComponent.kt b/java/tests/src/com/android/intentresolver/dagger/TestViewModelComponent.kt new file mode 100644 index 00000000..539b3f36 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/dagger/TestViewModelComponent.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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.intentresolver.dagger + +import dagger.Subcomponent + +/** A ViewModelComponent for tests which replaces ViewModelModule -> TestViewModelModule */ +@ViewModelScope +@Subcomponent(modules = [TestViewModelModule::class, ViewModelBinderModule::class]) +interface TestViewModelComponent : ViewModelComponent { + @Subcomponent.Builder + interface Builder : ViewModelComponent.Builder { + override fun build(): TestViewModelComponent + } +} diff --git a/java/tests/src/com/android/intentresolver/dagger/TestViewModelModule.kt b/java/tests/src/com/android/intentresolver/dagger/TestViewModelModule.kt new file mode 100644 index 00000000..28f4fa73 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/dagger/TestViewModelModule.kt @@ -0,0 +1,6 @@ +package com.android.intentresolver.dagger + +import dagger.Module + +/** Provides bindings shared among components within [@ViewModelScope][ViewModelScope]. */ +@Module abstract class TestViewModelModule diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java b/java/tests/src/com/android/intentresolver/logging/EventLogTest.java index aa42c24c..17452774 100644 --- a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java +++ b/java/tests/src/com/android/intentresolver/logging/EventLogTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.intentresolver; +package com.android.intentresolver.logging; import static com.google.common.truth.Truth.assertThat; @@ -32,10 +32,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.Intent; import android.metrics.LogMaker; -import com.android.intentresolver.ChooserActivityLogger.FrameworkStatsLogger; -import com.android.intentresolver.ChooserActivityLogger.SharesheetStandardEvent; -import com.android.intentresolver.ChooserActivityLogger.SharesheetStartedEvent; -import com.android.intentresolver.ChooserActivityLogger.SharesheetTargetSelectedEvent; +import com.android.intentresolver.logging.EventLog.FrameworkStatsLogger; +import com.android.intentresolver.logging.EventLog.SharesheetStandardEvent; +import com.android.intentresolver.logging.EventLog.SharesheetStartedEvent; +import com.android.intentresolver.logging.EventLog.SharesheetTargetSelectedEvent; import com.android.intentresolver.contentpreview.ContentPreviewType; import com.android.internal.logging.InstanceId; import com.android.internal.logging.MetricsLogger; @@ -53,17 +53,17 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public final class ChooserActivityLoggerTest { +public final class EventLogTest { @Mock private UiEventLogger mUiEventLog; @Mock private FrameworkStatsLogger mFrameworkLog; @Mock private MetricsLogger mMetricsLogger; - private ChooserActivityLogger mChooserLogger; + private EventLog mChooserLogger; @Before public void setUp() { //Mockito.reset(mUiEventLog, mFrameworkLog, mMetricsLogger); - mChooserLogger = new ChooserActivityLogger(mUiEventLog, mFrameworkLog, mMetricsLogger); + mChooserLogger = new EventLog(mUiEventLog, mFrameworkLog, mMetricsLogger); } @After @@ -151,7 +151,7 @@ public final class ChooserActivityLoggerTest { @Test public void testLogShareTargetSelected() { - final int targetType = ChooserActivityLogger.SELECTION_TYPE_SERVICE; + final int targetType = EventLog.SELECTION_TYPE_SERVICE; final String packageName = "com.test.foo"; final int positionPicked = 123; final int directTargetAlsoRanked = -1; @@ -189,7 +189,7 @@ public final class ChooserActivityLoggerTest { @Test public void testLogActionSelected() { - mChooserLogger.logActionSelected(ChooserActivityLogger.SELECTION_TYPE_COPY); + mChooserLogger.logActionSelected(EventLog.SELECTION_TYPE_COPY); verify(mFrameworkLog).write( eq(FrameworkStatsLog.RANKING_SELECTED), @@ -320,10 +320,10 @@ public final class ChooserActivityLoggerTest { @Test public void testDifferentLoggerInstancesUseDifferentInstanceIds() { ArgumentCaptor<Integer> idIntCaptor = ArgumentCaptor.forClass(Integer.class); - ChooserActivityLogger chooserLogger2 = - new ChooserActivityLogger(mUiEventLog, mFrameworkLog, mMetricsLogger); + EventLog chooserLogger2 = + new EventLog(mUiEventLog, mFrameworkLog, mMetricsLogger); - final int targetType = ChooserActivityLogger.SELECTION_TYPE_COPY; + final int targetType = EventLog.SELECTION_TYPE_COPY; final String packageName = "com.test.foo"; final int positionPicked = 123; final int directTargetAlsoRanked = -1; @@ -370,7 +370,7 @@ public final class ChooserActivityLoggerTest { ArgumentCaptor<Integer> idIntCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<InstanceId> idObjectCaptor = ArgumentCaptor.forClass(InstanceId.class); - final int targetType = ChooserActivityLogger.SELECTION_TYPE_COPY; + final int targetType = EventLog.SELECTION_TYPE_COPY; final String packageName = "com.test.foo"; final int positionPicked = 123; final int directTargetAlsoRanked = -1; @@ -403,20 +403,20 @@ public final class ChooserActivityLoggerTest { @Test public void testTargetSelectionCategories() { - assertThat(ChooserActivityLogger.getTargetSelectionCategory( - ChooserActivityLogger.SELECTION_TYPE_SERVICE)) + assertThat(EventLog.getTargetSelectionCategory( + EventLog.SELECTION_TYPE_SERVICE)) .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET); - assertThat(ChooserActivityLogger.getTargetSelectionCategory( - ChooserActivityLogger.SELECTION_TYPE_APP)) + assertThat(EventLog.getTargetSelectionCategory( + EventLog.SELECTION_TYPE_APP)) .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET); - assertThat(ChooserActivityLogger.getTargetSelectionCategory( - ChooserActivityLogger.SELECTION_TYPE_STANDARD)) + assertThat(EventLog.getTargetSelectionCategory( + EventLog.SELECTION_TYPE_STANDARD)) .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET); - assertThat(ChooserActivityLogger.getTargetSelectionCategory( - ChooserActivityLogger.SELECTION_TYPE_COPY)).isEqualTo(0); - assertThat(ChooserActivityLogger.getTargetSelectionCategory( - ChooserActivityLogger.SELECTION_TYPE_NEARBY)).isEqualTo(0); - assertThat(ChooserActivityLogger.getTargetSelectionCategory( - ChooserActivityLogger.SELECTION_TYPE_EDIT)).isEqualTo(0); + assertThat(EventLog.getTargetSelectionCategory( + EventLog.SELECTION_TYPE_COPY)).isEqualTo(0); + assertThat(EventLog.getTargetSelectionCategory( + EventLog.SELECTION_TYPE_NEARBY)).isEqualTo(0); + assertThat(EventLog.getTargetSelectionCategory( + EventLog.SELECTION_TYPE_EDIT)).isEqualTo(0); } } |