Merge changes from topic "pss-app-selector-screenshot" into tm-qpr-dev

* changes:
  [Partial Screensharing] Add app selector activity screenshot test
  [Partial Screensharing] Update app selector (ChooserActivity) header
This commit is contained in:
Nick Chameyev
2022-09-06 13:05:30 +00:00
committed by Android (Google) Code Review
12 changed files with 426 additions and 62 deletions

View File

@@ -63,6 +63,7 @@ import android.database.DataSetObserver;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimatedVectorDrawable;
@@ -146,6 +147,7 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@@ -281,6 +283,7 @@ public class ChooserActivity extends ResolverActivity implements
private long mQueriedSharingShortcutsTimeMs; private long mQueriedSharingShortcutsTimeMs;
private int mCurrAvailableWidth = 0; private int mCurrAvailableWidth = 0;
private Insets mLastAppliedInsets = null;
private int mLastNumberOfChildren = -1; private int mLastNumberOfChildren = -1;
private int mMaxTargetsPerRow = 1; private int mMaxTargetsPerRow = 1;
@@ -1619,6 +1622,8 @@ public class ChooserActivity extends ResolverActivity implements
if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) { if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor(); mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
} }
mPersonalAppPredictor = null;
mWorkAppPredictor = null;
} }
@Override // ResolverListCommunicator @Override // ResolverListCommunicator
@@ -2531,7 +2536,11 @@ public class ChooserActivity extends ResolverActivity implements
|| gridAdapter.calculateChooserTargetWidth(availableWidth) || gridAdapter.calculateChooserTargetWidth(availableWidth)
|| recyclerView.getAdapter() == null || recyclerView.getAdapter() == null
|| availableWidth != mCurrAvailableWidth; || availableWidth != mCurrAvailableWidth;
boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
if (isLayoutUpdated if (isLayoutUpdated
|| insetsChanged
|| mLastNumberOfChildren != recyclerView.getChildCount()) { || mLastNumberOfChildren != recyclerView.getChildCount()) {
mCurrAvailableWidth = availableWidth; mCurrAvailableWidth = availableWidth;
if (isLayoutUpdated) { if (isLayoutUpdated) {
@@ -2552,7 +2561,7 @@ public class ChooserActivity extends ResolverActivity implements
return; return;
} }
if (mLastNumberOfChildren == recyclerView.getChildCount()) { if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
return; return;
} }
@@ -2563,6 +2572,7 @@ public class ChooserActivity extends ResolverActivity implements
int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter); int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
mResolverDrawerLayout.setCollapsibleHeightReserved(offset); mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
mEnterTransitionAnimationDelegate.markOffsetCalculated(); mEnterTransitionAnimationDelegate.markOffsetCalculated();
mLastAppliedInsets = mSystemWindowInsets;
}); });
} }
} }
@@ -3055,7 +3065,12 @@ public class ChooserActivity extends ResolverActivity implements
mChooserMultiProfilePagerAdapter.setupContainerPadding( mChooserMultiProfilePagerAdapter.setupContainerPadding(
getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container)); getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
} }
return super.onApplyWindowInsets(v, insets);
WindowInsets result = super.onApplyWindowInsets(v, insets);
if (mResolverDrawerLayout != null) {
mResolverDrawerLayout.requestLayout();
}
return result;
} }
private void setHorizontalScrollingEnabled(boolean enabled) { private void setHorizontalScrollingEnabled(boolean enabled) {

View File

@@ -79,6 +79,11 @@
<java-symbol type="id" name="deny_button" /> <java-symbol type="id" name="deny_button" />
<java-symbol type="id" name="description" /> <java-symbol type="id" name="description" />
<java-symbol type="id" name="divider" /> <java-symbol type="id" name="divider" />
<java-symbol type="id" name="drag" />
<java-symbol type="id" name="profile_pager" />
<java-symbol type="id" name="chooser_header" />
<java-symbol type="id" name="content_preview_container" />
<java-symbol type="id" name="profile_tabhost" />
<java-symbol type="id" name="edit_query" /> <java-symbol type="id" name="edit_query" />
<java-symbol type="id" name="edittext_container" /> <java-symbol type="id" name="edittext_container" />
<java-symbol type="id" name="expand_activities_button" /> <java-symbol type="id" name="expand_activities_button" />

View File

@@ -0,0 +1,25 @@
<!--
~ Copyright (C) 2022 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v14c0,1.11 0.89,2 2,2h18c1.11,0 2,-0.89 2,-2L23,5c0,-1.11 -0.89,-2 -2,-2zM21,19.02L3,19.02L3,4.98h18v14.04zM8,12l4,-4 4,4 -1.41,1.41L13,11.83L13,16h-2v-4.17l-1.59,1.59L8,12z"/>
</vector>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<com.android.internal.widget.ResolverDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
androidprv:maxCollapsedHeight="0dp"
androidprv:maxCollapsedHeightSmall="56dp"
androidprv:maxWidth="@*android:dimen/chooser_width"
android:id="@*android:id/contentPanel">
<LinearLayout
android:id="@*android:id/chooser_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
androidprv:layout_alwaysShow="true"
android:gravity="center"
android:elevation="0dp"
android:background="@*android:drawable/bottomsheet_background">
<ImageView
android:id="@*android:id/icon"
android:layout_width="@dimen/media_projection_app_selector_icon_size"
android:layout_height="@dimen/media_projection_app_selector_icon_size"
android:layout_marginTop="@*android:dimen/chooser_edge_margin_normal"
android:layout_marginBottom="@*android:dimen/chooser_edge_margin_normal"
android:importantForAccessibility="no"
android:tint="?android:attr/textColorPrimary"/>
<TextView android:id="@*android:id/title"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:paddingBottom="@*android:dimen/chooser_view_spacing"
android:paddingLeft="24dp"
android:paddingRight="24dp"/>
</LinearLayout>
<FrameLayout
android:id="@*android:id/content_preview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<TabHost
android:id="@*android:id/profile_tabhost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="?android:attr/colorBackground">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TabWidget
android:id="@*android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
</TabWidget>
<FrameLayout
android:id="@*android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.android.internal.app.ResolverViewPager
android:id="@*android:id/profile_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
</LinearLayout>
</TabHost>
</com.android.internal.widget.ResolverDrawerLayout>

View File

@@ -1440,6 +1440,8 @@
<dimen name="fgs_manager_list_top_spacing">12dp</dimen> <dimen name="fgs_manager_list_top_spacing">12dp</dimen>
<dimen name="media_projection_app_selector_icon_size">32dp</dimen>
<!-- Dream overlay related dimensions --> <!-- Dream overlay related dimensions -->
<dimen name="dream_overlay_status_bar_height">60dp</dimen> <dimen name="dream_overlay_status_bar_height">60dp</dimen>
<dimen name="dream_overlay_status_bar_margin">40dp</dimen> <dimen name="dream_overlay_status_bar_margin">40dp</dimen>

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 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.systemui.testing.screenshot
import android.app.Activity
import android.graphics.Color
import android.view.View
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import platform.test.screenshot.*
/**
* A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
*/
class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
)
private val delegateRule =
RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
private val matcher = UnitTestBitmapMatcher
override fun apply(base: Statement, description: Description): Statement {
return delegateRule.apply(base, description)
}
/**
* Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
* the context of [emulationSpec].
*/
fun screenshotTest(goldenIdentifier: String, view: View) {
view.removeElevationRecursively()
ScreenshotRuleAsserter.Builder(screenshotRule)
.setScreenshotProvider { view.toBitmap() }
.withMatcher(matcher)
.build()
.assertGoldenImage(goldenIdentifier)
}
/**
* Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
* in the context of [emulationSpec].
*/
fun activityScreenshotTest(
goldenIdentifier: String,
activity: Activity,
) {
val rootView = activity.window.decorView
// Hide system bars, remove insets, focus and make sure device-specific cutouts
// don't affect screenshots
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val window = activity.window
window.setDecorFitsSystemWindows(false)
WindowInsetsControllerCompat(window, rootView).apply {
hide(WindowInsetsCompat.Type.systemBars())
systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
window.attributes =
window.attributes.apply {
layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
rootView.removeInsetsRecursively()
activity.currentFocus?.clearFocus()
}
screenshotTest(goldenIdentifier, rootView)
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2022 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.systemui.testing.screenshot
import android.app.Activity
import android.content.Intent
import androidx.core.app.AppComponentFactory
class TestAppComponentFactory : AppComponentFactory() {
init {
instance = this
}
private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
fun clearOverrides() {
overrides.clear()
}
fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
overrides[activity.name] = provider
}
override fun instantiateActivityCompat(
cl: ClassLoader,
className: String,
intent: Intent?
): Activity {
return overrides
.getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
.invoke()
}
companion object {
private var instance: TestAppComponentFactory? = null
fun getInstance(): TestAppComponentFactory =
instance
?: error(
"TestAppComponentFactory is not initialized, " +
"did you specify it in the manifest?"
)
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 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.systemui.testing.screenshot
import android.view.View
import android.view.ViewGroup
import com.android.systemui.util.children
import android.view.WindowInsets
/**
* Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
* disable it for any view in the hierarchy.
*/
fun View.removeElevationRecursively() {
this.elevation = 0f
(this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
}
/**
* Different devices could have different insets (e.g. different height of the navigation bar or
* taskbar). This method dispatches empty insets to the whole view hierarchy and removes
* the original listener, so the views won't receive real insets.
*/
fun View.removeInsetsRecursively() {
this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
this.setOnApplyWindowInsetsListener(null)
(this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
}

View File

@@ -1,10 +1,12 @@
package com.android.systemui.testing.screenshot package com.android.systemui.testing.screenshot
import android.annotation.WorkerThread
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.HardwareRenderer
import android.graphics.Rect import android.graphics.Rect
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
@@ -19,8 +21,13 @@ import androidx.annotation.RequiresApi
import androidx.concurrent.futures.ResolvableFuture import androidx.concurrent.futures.ResolvableFuture
import androidx.test.annotation.ExperimentalTestApi import androidx.test.annotation.ExperimentalTestApi
import androidx.test.core.internal.os.HandlerExecutor import androidx.test.core.internal.os.HandlerExecutor
import androidx.test.espresso.Espresso
import androidx.test.platform.graphics.HardwareRendererCompat import androidx.test.platform.graphics.HardwareRendererCompat
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.runBlocking
/* /*
* This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
@@ -61,6 +68,47 @@ fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
return bitmapFuture return bitmapFuture
} }
/**
* Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
* [captureToBitmap].
*/
@WorkerThread
@ExperimentalTestApi
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
fun View.toBitmap(window: Window? = null): Bitmap {
if (Looper.getMainLooper() == Looper.myLooper()) {
error("toBitmap() can't be called from the main thread")
}
if (!HardwareRenderer.isDrawingEnabled()) {
error("Hardware rendering is not enabled")
}
// Make sure we are idle.
Espresso.onIdle()
val mainExecutor = context.mainExecutor
return runBlocking {
suspendCoroutine { continuation ->
Futures.addCallback(
captureToBitmap(window),
object : FutureCallback<Bitmap> {
override fun onSuccess(result: Bitmap) {
continuation.resumeWith(Result.success(result))
}
override fun onFailure(t: Throwable) {
continuation.resumeWith(Result.failure(t))
}
},
// We know that we are not on the main thread, so we can block the current
// thread and wait for the result in the main thread.
mainExecutor,
)
}
}
}
/** /**
* Trigger a redraw of the given view. * Trigger a redraw of the given view.
* *

View File

@@ -19,21 +19,13 @@ package com.android.systemui.testing.screenshot
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.HardwareRenderer
import android.os.Looper
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.Window
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.test.espresso.Espresso
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.rules.RuleChain import org.junit.rules.RuleChain
import org.junit.rules.TestRule import org.junit.rules.TestRule
@@ -89,6 +81,8 @@ class ViewScreenshotTestRule(
// Elevation/shadows is not deterministic when doing hardware rendering, so we disable // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
// it for any view in the hierarchy. // it for any view in the hierarchy.
window.decorView.removeElevationRecursively() window.decorView.removeElevationRecursively()
activity.currentFocus?.clearFocus()
} }
// We call onActivity again because it will make sure that our Activity is done measuring, // We call onActivity again because it will make sure that our Activity is done measuring,
@@ -150,53 +144,11 @@ class ViewScreenshotTestRule(
} }
} }
private fun View.removeElevationRecursively() {
this.elevation = 0f
if (this is ViewGroup) {
repeat(childCount) { i -> getChildAt(i).removeElevationRecursively() }
}
}
private fun Dialog.toBitmap(): Bitmap { private fun Dialog.toBitmap(): Bitmap {
val window = window val window = window
return window.decorView.toBitmap(window) return window.decorView.toBitmap(window)
} }
private fun View.toBitmap(window: Window? = null): Bitmap {
if (Looper.getMainLooper() == Looper.myLooper()) {
error("toBitmap() can't be called from the main thread")
}
if (!HardwareRenderer.isDrawingEnabled()) {
error("Hardware rendering is not enabled")
}
// Make sure we are idle.
Espresso.onIdle()
val mainExecutor = context.mainExecutor
return runBlocking {
suspendCoroutine { continuation ->
Futures.addCallback(
captureToBitmap(window),
object : FutureCallback<Bitmap> {
override fun onSuccess(result: Bitmap?) {
continuation.resumeWith(Result.success(result!!))
}
override fun onFailure(t: Throwable) {
continuation.resumeWith(Result.failure(t))
}
},
// We know that we are not on the main thread, so we can block the current
// thread and wait for the result in the main thread.
mainExecutor,
)
}
}
}
enum class Mode(val layoutParams: LayoutParams) { enum class Mode(val layoutParams: LayoutParams) {
WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)), WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)), MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),

View File

@@ -23,35 +23,47 @@ import android.os.Binder
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.os.ResultReceiver import android.os.ResultReceiver
import android.view.View import android.os.UserHandle
import android.widget.ImageView
import com.android.internal.app.ChooserActivity import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo import com.android.internal.app.chooser.TargetInfo
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.util.AsyncActivityLauncher import com.android.systemui.util.AsyncActivityLauncher
import com.android.systemui.R; import com.android.systemui.R
import javax.inject.Inject import com.android.internal.R as AndroidR
class MediaProjectionAppSelectorActivity @Inject constructor( class MediaProjectionAppSelectorActivity constructor(
private val activityLauncher: AsyncActivityLauncher private val activityLauncher: AsyncActivityLauncher,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? = null
) : ChooserActivity() { ) : ChooserActivity() {
override fun getLayoutResource() =
R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) { public override fun onCreate(bundle: Bundle?) {
val queryIntent = Intent(Intent.ACTION_MAIN) val queryIntent = Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER) .addCategory(Intent.CATEGORY_LAUNCHER)
intent.putExtra(Intent.EXTRA_INTENT, queryIntent) intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
// TODO(b/235465652) Use resource lexeme // TODO(b/240939253): update copies
intent.putExtra(Intent.EXTRA_TITLE, "Record or cast an app") val title = getString(R.string.media_projection_dialog_service_title)
intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle) super.onCreate(bundle)
// TODO(b/235465652) we should update VisD of the title and add an icon requireViewById<ImageView>(AndroidR.id.icon).setImageResource(R.drawable.ic_present_to_all)
findViewById<View>(R.id.title)?.visibility = View.VISIBLE
} }
override fun appliedThemeResId(): Int = override fun appliedThemeResId(): Int =
R.style.Theme_SystemUI_MediaProjectionAppSelector R.style.Theme_SystemUI_MediaProjectionAppSelector
override fun createListController(userHandle: UserHandle): ResolverListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
override fun startSelected(which: Int, always: Boolean, filtered: Boolean) { override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return

View File

@@ -18,8 +18,10 @@ package com.android.systemui.media.dagger
import android.app.Activity import android.app.Activity
import com.android.systemui.media.MediaProjectionAppSelectorActivity import com.android.systemui.media.MediaProjectionAppSelectorActivity
import com.android.systemui.util.AsyncActivityLauncher
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap import dagger.multibindings.IntoMap
@@ -29,7 +31,17 @@ abstract class MediaProjectionModule {
@Binds @Binds
@IntoMap @IntoMap
@ClassKey(MediaProjectionAppSelectorActivity::class) @ClassKey(MediaProjectionAppSelectorActivity::class)
abstract fun provideMediaProjectionAppSelectorActivity( abstract fun bindMediaProjectionAppSelectorActivity(
activity: MediaProjectionAppSelectorActivity): Activity activity: MediaProjectionAppSelectorActivity): Activity
companion object {
@Provides
fun provideMediaProjectionAppSelectorActivity(
activityLauncher: AsyncActivityLauncher
): MediaProjectionAppSelectorActivity {
return MediaProjectionAppSelectorActivity(
activityLauncher
)
}
}
} }