Merge "Ensure destinations are linked properly on nav back stack" into androidx-master-dev
diff --git a/animation/build.gradle b/animation/build.gradle
index 743350e..f0a633e 100644
--- a/animation/build.gradle
+++ b/animation/build.gradle
@@ -26,7 +26,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0-rc01")
implementation(project(":core"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT, libs.exclude_for_espresso)
androidTestImplementation(ANDROIDX_TEST_RULES, libs.exclude_for_espresso)
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/testutils/BaseTestActivity.java b/appcompat/src/androidTest/java/androidx/appcompat/testutils/BaseTestActivity.java
index 1b20e5c..1827727 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/testutils/BaseTestActivity.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/testutils/BaseTestActivity.java
@@ -27,6 +27,7 @@
import androidx.appcompat.app.AppCompatCallback;
import androidx.appcompat.test.R;
import androidx.appcompat.view.ActionMode;
+import androidx.testutils.LocaleTestUtils;
import androidx.testutils.RecreatedAppCompatActivity;
public abstract class BaseTestActivity extends RecreatedAppCompatActivity {
@@ -48,10 +49,19 @@
private boolean mDestroyed;
private AppCompatCallback mAppCompatCallback;
+ private static final String EXTRA_LANGUAGE = "language";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ final LocaleTestUtils locale = new LocaleTestUtils(this);
+ if (getIntent().hasExtra(EXTRA_LANGUAGE)) {
+ locale.setLocale(LocaleTestUtils.RTL_LANGUAGE);
+ } else {
+ locale.setLocale(LocaleTestUtils.DEFAULT_TEST_LANGUAGE);
+ }
+
overridePendingTransition(0, 0);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
final int contentView = getContentViewLayoutResId();
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
index 7c45fa9..dea4a0f6 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
@@ -32,8 +32,11 @@
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
+import android.os.Build;
import android.os.SystemClock;
import android.view.View;
@@ -51,10 +54,14 @@
import androidx.test.espresso.action.Swipe;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.testutils.LocaleTestUtils;
import androidx.testutils.PollingCheck;
+import org.hamcrest.Description;
import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Test;
@@ -65,7 +72,7 @@
@LargeTest
public class AppCompatSpinnerTest
extends AppCompatBaseViewTest<AppCompatSpinnerActivity, AppCompatSpinner> {
- private static final String EARTH = "Earth";
+ private static final String >
private Instrumentation mInstrumentation;
public AppCompatSpinnerTest() {
@@ -138,13 +145,11 @@
waitUntilPopupIsHidden(spinner);
}
- @LargeTest
@Test
public void testPopupThemingFromXmlAttribute() {
verifySpinnerPopupTheming(R.id.view_magenta_themed_popup, R.color.test_magenta, true);
}
- @LargeTest
@Test
public void testUnthemedPopupRuntimeTheming() {
final AppCompatSpinner spinner =
@@ -158,7 +163,6 @@
verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_green, false);
}
- @LargeTest
@Test
public void testThemedPopupRuntimeTheming() {
final AppCompatSpinner spinner =
@@ -191,13 +195,11 @@
assertThat(dialogPopup.mPopup, instanceOf(AlertDialog.class));
}
- @LargeTest
@Test
public void testChangeOrientationDialogPopupPersists() {
verifyChangeOrientationPopupPersists(R.id.spinner_dialog_popup, true);
}
- @LargeTest
@Test
public void testChangeOrientationDropdownPopupPersists() {
verifyChangeOrientationPopupPersists(R.id.spinner_dropdown_popup, false);
@@ -213,10 +215,11 @@
Instrumentation.ActivityMonitor monitor =
new Instrumentation.ActivityMonitor(mActivity.getClass().getName(), null, false);
mInstrumentation.addMonitor(monitor);
+
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
SystemClock.sleep(250);
- mInstrumentation.waitForIdleSync();
+ mInstrumentation.waitForIdleSync();
mActivity = (AppCompatSpinnerActivity) mInstrumentation.waitForMonitor(monitor);
// Now we can get the new (post-rotation) instance of our spinner
@@ -225,7 +228,6 @@
assertTrue(newSpinner.getInternalPopup().isShowing());
}
- @LargeTest
@Test
public void testSlowScroll() {
final AppCompatSpinner spinner = mContainer
@@ -247,6 +249,69 @@
onView(withText(secondItem)).check(doesNotExist());
}
+ @Test
+ public void testHorizontalOffset() {
+ checkOffsetIsCorrect(500, false, false);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public void testHorizontalOffsetRtl() {
+ setRtl();
+ checkOffsetIsCorrect(200, false, true);
+ }
+
+ @Test
+ public void testVerticalOffset() {
+ checkOffsetIsCorrect(100, true, false);
+ }
+
+ private void checkOffsetIsCorrect(
+ final int offset,
+ final boolean isVerticalOffset,
+ final boolean isRtl) {
+ int spinnerId = R.id.spinner_dropdown_popup_small;
+
+ final AppCompatSpinner spinner = mContainer.findViewById(spinnerId);
+ if (isVerticalOffset) {
+ spinner.setDropDownVerticalOffset(offset);
+ } else {
+ spinner.setDropDownHorizontalOffset(offset);
+ }
+
+ onView(withId(spinnerId)).perform(click());
+ SystemClock.sleep(250);
+
+ int computedOffset;
+ if (isVerticalOffset) {
+ int[] location = new int[2];
+ spinner.getLocationOnScreen(location);
+
+ computedOffset = location[1] + offset;
+ } else {
+ if (isRtl) {
+ int[] location = new int[2];
+ spinner.getLocationOnScreen(location);
+ final AppCompatSpinner.SpinnerPopup spinnerPopup = spinner.getInternalPopup();
+ AppCompatSpinner.DropdownPopup dropdownPopup =
+ (AppCompatSpinner.DropdownPopup) (spinnerPopup);
+ final int popupWidth = dropdownPopup.getWidth();
+ final int spinnerWidth = spinner.getWidth();
+
+ computedOffset = location[0] + (spinnerWidth - popupWidth - offset);
+ } else {
+ computedOffset = offset;
+ }
+ }
+
+ onView(withText(ONE)).check(matches(
+ hasOffset(
+ computedOffset,
+ isVerticalOffset ? "has vertical offset" : "has horizontal offset",
+ isVerticalOffset)
+ ));
+ }
+
private ViewAction slowScrollPopup() {
return new GeneralSwipeAction(Swipe.SLOW,
new CoordinatesProvider() {
@@ -314,4 +379,50 @@
}
});
}
+
+ private Matcher<View> hasOffset(
+ final int offset,
+ final String desc,
+ final boolean isVerticalOffset) {
+ return new TypeSafeMatcher<View>() {
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(desc);
+ }
+
+ @Override
+ protected boolean matchesSafely(View view) {
+ if (view.getParent() instanceof DropDownListView) {
+ final DropDownListView dropDownListView = (DropDownListView) (view.getParent());
+ int[] location = new int[2];
+ dropDownListView.getLocationOnScreen(location);
+ dropDownListView.getWidth();
+ return location[isVerticalOffset ? 1 : 0] == offset;
+ }
+
+ return false;
+ }
+ };
+ }
+
+ private void setRtl() {
+ final Context context = mInstrumentation.getTargetContext();
+
+ mActivity.finish();
+ final Intent intent = new Intent(context, AppCompatSpinnerActivity.class);
+ intent.putExtra("language", LocaleTestUtils.RTL_LANGUAGE);
+
+ Instrumentation.ActivityMonitor monitor =
+ new Instrumentation.ActivityMonitor(mActivity.getClass().getName(), null, false);
+ mInstrumentation.addMonitor(monitor);
+
+ mActivity = mActivityTestRule.launchActivity(intent);
+
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.waitForMonitor(monitor);
+
+ mContainer = mActivity.findViewById(R.id.container);
+ mResources = mActivity.getResources();
+ }
}
diff --git a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
index 534048e..2378f9b 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
@@ -116,6 +116,15 @@
android:entries="@array/numbers_array"
android:focusable="false"
android:spinnerMode="dropdown" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/spinner_dropdown_popup_small"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:entries="@array/numbers_array_small"
+ android:popupBackground="@null"
+ android:focusable="false"
+ android:spinnerMode="dropdown" />
</LinearLayout>
</ScrollView>
diff --git a/appcompat/src/androidTest/res/values/strings.xml b/appcompat/src/androidTest/res/values/strings.xml
index 0188024..9b0c971 100644
--- a/appcompat/src/androidTest/res/values/strings.xml
+++ b/appcompat/src/androidTest/res/values/strings.xml
@@ -120,6 +120,11 @@
<item>38</item>
<item>39</item>
</string-array>
+ <string-array name="numbers_array_small">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ </string-array>
<string name="night_mode">DAY</string>
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
index 1033e46..01d7375 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
@@ -347,6 +347,7 @@
@Override
public void setDropDownHorizontalOffset(int pixels) {
if (mPopup != null) {
+ mPopup.setHorizontalOriginalOffset(pixels);
mPopup.setHorizontalOffset(pixels);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.setDropDownHorizontalOffset(pixels);
@@ -836,6 +837,8 @@
void setBackgroundDrawable(Drawable bg);
void setVerticalOffset(int px);
void setHorizontalOffset(int px);
+ void setHorizontalOriginalOffset(int px);
+ int getHorizontalOriginalOffset();
Drawable getBackground();
int getVerticalOffset();
int getHorizontalOffset();
@@ -933,12 +936,24 @@
public int getHorizontalOffset() {
return 0;
}
+
+ @Override
+ public void setHorizontalOriginalOffset(int px) {
+ Log.e(TAG, "Cannot set horizontal (original) offset for MODE_DIALOG, ignoring");
+ }
+
+ @Override
+ public int getHorizontalOriginalOffset() {
+ return 0;
+ }
}
- private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
+ @VisibleForTesting
+ class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
private CharSequence mHintText;
ListAdapter mAdapter;
private final Rect mVisibleRect = new Rect();
+ private int mOriginalHorizontalOffset;
public DropdownPopup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -1007,9 +1022,10 @@
setContentWidth(mDropDownWidth);
}
if (ViewUtils.isLayoutRtl(AppCompatSpinner.this)) {
- hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
+ hOffset += spinnerWidth - spinnerPaddingRight - getWidth()
+ - getHorizontalOriginalOffset();
} else {
- hOffset += spinnerPaddingLeft;
+ hOffset += spinnerPaddingLeft + getHorizontalOriginalOffset();
}
setHorizontalOffset(hOffset);
}
@@ -1075,5 +1091,15 @@
boolean isVisibleToUser(View view) {
return ViewCompat.isAttachedToWindow(view) && view.getGlobalVisibleRect(mVisibleRect);
}
+
+ @Override
+ public void setHorizontalOriginalOffset(int px) {
+ mOriginalHorizontalOffset = px;
+ }
+
+ @Override
+ public int getHorizontalOriginalOffset() {
+ return mOriginalHorizontalOffset;
+ }
}
}
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
index b52de5c..e2a8f9b 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper.java
@@ -719,25 +719,31 @@
@RequiresApi(23)
private StaticLayout createStaticLayoutForMeasuring(CharSequence text,
Layout.Alignment alignment, int availableWidth, int maxLines) {
- // Can use the StaticLayout.Builder (along with TextView params added in or after
- // API 23) to construct the layout.
- final TextDirectionHeuristic textDirectionHeuristic = invokeAndReturnWithDefault(
- mTextView, "getTextDirectionHeuristic",
- TextDirectionHeuristics.FIRSTSTRONG_LTR);
final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
text, 0, text.length(), mTempTextPaint, availableWidth);
- return layoutBuilder.setAlignment(alignment)
+ layoutBuilder.setAlignment(alignment)
.setLineSpacing(
mTextView.getLineSpacingExtra(),
mTextView.getLineSpacingMultiplier())
.setIncludePad(mTextView.getIncludeFontPadding())
.setBreakStrategy(mTextView.getBreakStrategy())
.setHyphenationFrequency(mTextView.getHyphenationFrequency())
- .setMaxLines(maxLines == -1 ? Integer.MAX_VALUE : maxLines)
- .setTextDirection(textDirectionHeuristic)
- .build();
+ .setMaxLines(maxLines == -1 ? Integer.MAX_VALUE : maxLines);
+
+ try {
+ // Can use the StaticLayout.Builder (along with TextView params added in or after
+ // API 23) to construct the layout.
+ final TextDirectionHeuristic textDirectionHeuristic = invokeAndReturnWithDefault(
+ mTextView, "getTextDirectionHeuristic",
+ TextDirectionHeuristics.FIRSTSTRONG_LTR);
+ layoutBuilder.setTextDirection(textDirectionHeuristic);
+ } catch (ClassCastException e) {
+ // On some devices this exception happens, details: b/127137059.
+ Log.w(TAG, "Failed to obtain TextDirectionHeuristic, auto size may be incorrect");
+ }
+ return layoutBuilder.build();
}
@RequiresApi(16)
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt
index 05df30a..a75b4f4 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt
@@ -42,13 +42,16 @@
fun execSync(
adbCmd: String,
deviceId: String? = null,
- shouldThrow: Boolean = true
+ shouldThrow: Boolean = true,
+ silent: Boolean = false
): ProcessResult {
val subCmd = adbCmd.trim().split(Regex("\\s+")).toTypedArray()
val adbArgs = if (!deviceId.isNullOrEmpty()) arrayOf("-s", deviceId) else emptyArray()
val cmd = arrayOf(adbPath, *adbArgs, *subCmd)
- logger.log(LogLevel.INFO, cmd.joinToString(" "))
+ if (!silent) {
+ logger.log(LogLevel.INFO, cmd.joinToString(" "))
+ }
val process = Runtime.getRuntime().exec(cmd)
if (!process.waitFor(5, TimeUnit.SECONDS)) {
@@ -58,8 +61,13 @@
val stdout = process.inputStream.bufferedReader().use { it.readText() }
val stderr = process.errorStream.bufferedReader().use { it.readText() }
- logger.log(LogLevel.QUIET, stdout)
- logger.log(LogLevel.WARN, stderr)
+ if (!stdout.isBlank() && !silent) {
+ logger.log(LogLevel.QUIET, stdout.trim())
+ }
+
+ if (!stderr.isBlank() && shouldThrow && !silent) {
+ logger.log(LogLevel.ERROR, stderr.trim())
+ }
if (shouldThrow && process.exitValue() != 0) {
throw GradleException(stderr)
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
index c65034d..b006f02 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
@@ -35,7 +35,7 @@
// Skip "adb root" if already rooted as it will fail.
if (adb.execSync("shell su exit", shouldThrow = false).exitValue != 0) {
- adb.execSync("root")
+ adb.execSync("root", silent = true)
}
val dest = "/data/local/tmp/lockClocks.sh"
diff --git a/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh b/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
index b22634d..89e24428 100755
--- a/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
+++ b/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
@@ -137,8 +137,10 @@
echo 0 > ${CPU_BASE}/cpu${cpu}/online
done
- echo "\nLocked CPUs ${enableIndices// /,} to $chosenFreq / $maxFreq KHz"
+ echo "=================================="
+ echo "Locked CPUs ${enableIndices// /,} to $chosenFreq / $maxFreq KHz"
echo "Disabled CPUs ${disableIndices// /,}"
+ echo "=================================="
}
# If we have a Qualcomm GPU, find its max frequency, and lock to
@@ -146,12 +148,12 @@
function_lock_gpu_kgsl() {
if [ ! -d /sys/class/kgsl/kgsl-3d0/ ]; then
# not kgsl, abort
- echo "\nCurrently don't support locking GPU clocks of $MODEL ($DEVICE)"
+ echo "Currently don't support locking GPU clocks of $MODEL ($DEVICE)"
return -1
fi
if [ ${DEVICE} == "walleye" ] || [ ${DEVICE} == "taimen" ]; then
# Workaround crash
- echo "\nUnable to lock GPU clocks of $MODEL ($DEVICE)"
+ echo "Unable to lock GPU clocks of $MODEL ($DEVICE)"
return -1
fi
@@ -218,7 +220,7 @@
echo "index = $chosenIndex"
exit -1
fi
- echo "\nLocked GPU to $chosenFreq / $gpuMaxFreq Hz"
+ echo "Locked GPU to $chosenFreq / $gpuMaxFreq Hz"
}
# kill processes that manage thermals / scaling
@@ -235,7 +237,7 @@
if [ ${DEVICE} == "marlin" ] || [ ${DEVICE} == "sailfish" ]; then
echo 13763 > /sys/class/devfreq/soc:qcom,gpubw/max_freq
else
- echo "\nUnable to lock memory bus of $MODEL ($DEVICE)."
+ echo "Unable to lock memory bus of $MODEL ($DEVICE)."
fi
-echo "\n$DEVICE clocks have been locked - to reset, reboot the device\n"
+echo "$DEVICE clocks have been locked - to reset, reboot the device"
diff --git a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
index ab22d01..13e2683 100644
--- a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
+++ b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
@@ -18,7 +18,6 @@
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.UnexpectedBuildFailure
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -78,10 +77,6 @@
.withPluginClasspath()
}
- @After
- fun tearDown() {
- }
-
@Test
fun applyPluginAppProject() {
buildFile.writeText(
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
index 97ea30b..32df3ab 100644
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
+++ b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
@@ -151,55 +151,52 @@
override fun apply(base: Statement, description: Description): Statement {
return RuleChain
.outerRule(GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE))
- .around { base, description ->
- object : Statement() {
- @Throws(Throwable::class)
- override fun evaluate() {
- applied = true
- var invokeMethodName = description.methodName
- Log.i(TAG, "Running ${description.className}#$invokeMethodName")
-
- // validate and simplify the function name.
- // First, remove the "test" prefix which normally comes from CTS test.
- // Then make sure the [subTestName] is valid, not just numbers like [0].
- if (invokeMethodName.startsWith("test")) {
- assertTrue(
- "The test name $invokeMethodName is too short",
- invokeMethodName.length > 5
- )
- invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase() +
- invokeMethodName.substring(5)
- }
-
- val index = invokeMethodName.lastIndexOf('[')
- if (index > 0) {
- val allDigits =
- invokeMethodName.substring(index + 1, invokeMethodName.length - 1)
- .all { Character.isDigit(it) }
- assertFalse(
- "The name in [] can't contain only digits for $invokeMethodName",
- allDigits
- )
- }
-
- base.evaluate()
-
- val fullTestName = WarningState.WARNING_PREFIX +
- description.testClass.simpleName + "." + invokeMethodName
- internalState.sendStatus(fullTestName)
-
- ResultWriter.appendStats(
- internalState.getReport(
- testName = WarningState.WARNING_PREFIX + invokeMethodName,
- className = description.className
- )
- )
- }
- }
- }
+ .around(::applyInternal)
.apply(base, description)
}
+ private fun applyInternal(base: Statement, description: Description) = Statement {
+ applied = true
+ var invokeMethodName = description.methodName
+ Log.i(TAG, "Running ${description.className}#$invokeMethodName")
+
+ // validate and simplify the function name.
+ // First, remove the "test" prefix which normally comes from CTS test.
+ // Then make sure the [subTestName] is valid, not just numbers like [0].
+ if (invokeMethodName.startsWith("test")) {
+ assertTrue(
+ "The test name $invokeMethodName is too short",
+ invokeMethodName.length > 5
+ )
+ invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase() +
+ invokeMethodName.substring(5)
+ }
+
+ val index = invokeMethodName.lastIndexOf('[')
+ if (index > 0) {
+ val allDigits =
+ invokeMethodName.substring(index + 1, invokeMethodName.length - 1)
+ .all { Character.isDigit(it) }
+ assertFalse(
+ "The name in [] can't contain only digits for $invokeMethodName",
+ allDigits
+ )
+ }
+
+ base.evaluate()
+
+ val fullTestName = WarningState.WARNING_PREFIX +
+ description.testClass.simpleName + "." + invokeMethodName
+ internalState.sendStatus(fullTestName)
+
+ ResultWriter.appendStats(
+ internalState.getReport(
+ testName = WarningState.WARNING_PREFIX + invokeMethodName,
+ className = description.className
+ )
+ )
+ }
+
internal companion object {
private const val TAG = "BenchmarkRule"
}
@@ -235,3 +232,7 @@
block(localScope)
}
}
+
+internal fun Statement(evaluate: () -> Unit) = object : Statement() {
+ override fun evaluate() = evaluate()
+}
\ No newline at end of file
diff --git a/browser/build.gradle b/browser/build.gradle
index f42d8cd..a9f7221 100644
--- a/browser/build.gradle
+++ b/browser/build.gradle
@@ -17,7 +17,7 @@
api(project(":core"))
api("androidx.annotation:annotation:1.1.0-rc01")
api(project(":interpolator"))
- api(project(":collection"))
+ api("androidx.collection:collection:1.1.0-rc01")
api(project(":legacy-support-core-ui"))
implementation(project(":concurrent:concurrent-futures"))
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 9df5551..da633e8 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -21,7 +21,7 @@
build_versions.kotlin = kotlin_override
logger.warn("USING OVERRIDDEN KOTLIN GRADLE PLUGIN DEPENDENCY " + build_versions.kotlin)
} else {
- build_versions.kotlin = '1.3.20'
+ build_versions.kotlin = '1.3.31'
}
build_versions.lint = '26.4.0'
build_versions.dokka = '0.9.17-g002'
diff --git a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
index 4b3e854..ea5a7ce 100644
--- a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
@@ -18,15 +18,12 @@
import androidx.build.Strategy.Prebuilts
import androidx.build.Strategy.TipOfTree
-import androidx.build.checkapi.ApiXmlConversionTask
-import androidx.build.checkapi.CheckApiTasks
import androidx.build.doclava.ChecksConfig
import androidx.build.doclava.DEFAULT_DOCLAVA_CONFIG
import androidx.build.doclava.DoclavaTask
import androidx.build.docs.ConcatenateFilesTask
import androidx.build.docs.GenerateDocsTask
import androidx.build.gradle.isRoot
-import androidx.build.jdiff.JDiffTask
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
@@ -48,7 +45,6 @@
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.api.tasks.util.PatternSet
import java.io.File
-import java.lang.IllegalStateException
import java.net.URLClassLoader
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -57,10 +53,6 @@
private const val DOCLAVA_DEPENDENCY = "com.android:doclava:1.0.6"
-private const val JDIFF_DEPENDENCY = "com.android:jdiff:1.1.0"
-private const val XML_PARSER_APIS_DEPENDENCY = "xerces:xmlParserAPIs:2.6.2"
-private const val XERCES_IMPL_DEPENDENCY = "xerces:xercesImpl:2.6.2"
-
data class DacOptions(val libraryroot: String, val dataname: String)
class DiffAndDocs private constructor(
@@ -76,7 +68,6 @@
private val docsTasks: MutableMap<String, TaskProvider<GenerateDocsTask>> = mutableMapOf()
private val aggregateOldApiTxtsTask: TaskProvider<ConcatenateFilesTask>
private val aggregateNewApiTxtsTask: TaskProvider<ConcatenateFilesTask>
- private val generateDiffsTask: TaskProvider<JDiffTask>
init {
val doclavaConfiguration = root.configurations.create("doclava")
@@ -126,55 +117,17 @@
task.dependsOn(docsTasks[TIP_OF_TREE.name])
}
- val docletClasspath = doclavaConfiguration.resolve()
val oldOutputTxt = File(root.docsDir(), "previous.txt")
aggregateOldApiTxtsTask = root.tasks.register("aggregateOldApiTxts",
ConcatenateFilesTask::class.java) {
it.Output = oldOutputTxt
}
- val oldApisTask = root.tasks.register("oldApisXml",
- ApiXmlConversionTask::class.java) {
- it.classpath = root.files(docletClasspath)
- it.dependsOn(doclavaConfiguration)
-
- it.inputApiFile = oldOutputTxt
- it.dependsOn(aggregateOldApiTxtsTask)
-
- it.outputApiXmlFile = File(root.docsDir(), "previous.xml")
- }
-
val newApiTxt = File(root.docsDir(), newVersion)
aggregateNewApiTxtsTask = root.tasks.register("aggregateNewApiTxts",
ConcatenateFilesTask::class.java) {
it.Output = newApiTxt
}
-
- val newApisTask = root.tasks.register("newApisXml",
- ApiXmlConversionTask::class.java) {
- it.classpath = root.files(docletClasspath)
-
- it.inputApiFile = newApiTxt
- it.dependsOn(aggregateNewApiTxtsTask)
-
- it.outputApiXmlFile = File(root.docsDir(), "$newVersion.xml")
- }
-
- val jdiffConfiguration = root.configurations.create("jdiff")
- jdiffConfiguration.dependencies.add(root.dependencies.create(JDIFF_DEPENDENCY))
- jdiffConfiguration.dependencies.add(root.dependencies.create(XML_PARSER_APIS_DEPENDENCY))
- jdiffConfiguration.dependencies.add(root.dependencies.create(XERCES_IMPL_DEPENDENCY))
-
- generateDiffsTask = createGenerateDiffsTask(root,
- oldApisTask,
- newApisTask,
- jdiffConfiguration)
-
- generateDiffsTask.configure { diffTask ->
- docsTasks.values.forEach { docs ->
- diffTask.dependsOn(docs)
- }
- }
}
companion object {
@@ -323,8 +276,6 @@
tipOfTreeTasks(extension) { task ->
registerJavaProjectForDocsTask(task, compileJava)
}
-
- registerJavaProjectForDocsTask(generateDiffsTask, compileJava)
}
/**
@@ -354,40 +305,6 @@
}
}
}
-
- private fun setupApiVersioningInDocsTasks(
- extension: AndroidXExtension,
- checkApiTasks: CheckApiTasks
- ) {
- rules.forEach { rules ->
- val project = extension.project
- val strategy = rules.resolve(extension)?.strategy
- val version = if (strategy is Prebuilts) {
- strategy.version
- } else {
- extension.project.version()
- }
- docsTasks[rules.name]!!.configure { docs ->
- // Track API change history.
- docs.addSinceFilesFrom(project.projectDir)
- // Associate current API surface with the Maven artifact.
- val artifact = "${project.group}:${project.name}:$version"
- docs.addArtifact(checkApiTasks.generateApi.get().apiFile!!.absolutePath, artifact)
- docs.dependsOn(checkApiTasks.generateApi)
- }
- }
- }
-
- private fun addCheckApiTasksToGraph(tasks: CheckApiTasks) {
- docsTasks.values.forEach { docs ->
- docs.configure {
- it.dependsOn(tasks.generateApi)
- }
- }
- anchorTask.configure {
- it.dependsOn(tasks.checkApi)
- }
- }
}
/**
@@ -442,68 +359,6 @@
}
}
-/**
- * Generates API diffs.
- * <p>
- * By default, diffs are generated for the delta between current.txt and the
- * next most recent X.Y.Z.txt API file. Behavior may be changed by specifying
- * one or both of -PtoApi and -PfromApi.
- * <p>
- * If both fromApi and toApi are specified, diffs will be generated for
- * fromApi -> toApi. For example, 25.0.0 -> 26.0.0 diffs could be generated by
- * using:
- * <br><code>
- * ./gradlew generateDiffs -PfromApi=25.0.0 -PtoApi=26.0.0
- * </code>
- * <p>
- * If only toApi is specified, it MUST be specified as X.Y.Z and diffs will be
- * generated for (release before toApi) -> toApi. For example, 24.2.0 -> 25.0.0
- * diffs could be generated by using:
- * <br><code>
- * ./gradlew generateDiffs -PtoApi=25.0.0
- * </code>
- * <p>
- * If only fromApi is specified, diffs will be generated for fromApi -> current.
- * For example, lastApiReview -> current diffs could be generated by using:
- * <br><code>
- * ./gradlew generateDiffs -PfromApi=lastApiReview
- * </code>
- * <p>
- */
-private fun createGenerateDiffsTask(
- project: Project,
- oldApiTask: TaskProvider<ApiXmlConversionTask>,
- newApiTask: TaskProvider<ApiXmlConversionTask>,
- jdiffConfig: Configuration
-): TaskProvider<JDiffTask> =
- project.tasks.register("generateDiffs", JDiffTask::class.java) {
- it.apply {
- // Base classpath is Android SDK, sub-projects add their own.
- classpath = androidJarFile(project)
-
- // JDiff properties.
- oldApiXmlFile = oldApiTask.get().outputApiXmlFile
- newApiXmlFile = newApiTask.get().outputApiXmlFile
-
- val newApi = newApiXmlFile.name.substringBeforeLast('.')
- val docsDir = File(project.rootProject.docsDir(), "public")
-
- newJavadocPrefix = "../../../../../reference/"
- destinationDir = File(docsDir,
- "online/sdk/support_api_diff/${project.name}/$newApi")
-
- // Javadoc properties.
- docletpath = jdiffConfig.resolve()
- title = "Support Library API Differences Report"
-
- exclude("**/R.java")
- dependsOn(oldApiTask, newApiTask, jdiffConfig)
- doLast {
- project.logger.lifecycle("generated diffs into $destinationDir")
- }
- }
- }
-
// Generates a distribution artifact for online docs.
private fun createDistDocsTask(
project: Project,
@@ -616,18 +471,6 @@
}
}
-private fun createGenerateLocalApiDiffsArchiveTask(
- project: Project,
- diffTask: TaskProvider<JDiffTask>
-): TaskProvider<Zip> = project.tasks.register("generateLocalApiDiffsArchive", Zip::class.java) {
- val docsDir = project.rootProject.docsDir()
- it.from(diffTask.map {
- it.destinationDir
- })
- it.destinationDirectory.set(File(docsDir, "online/sdk/support_api_diff/${project.name}"))
- it.to("${project.version}.zip")
-}
-
private fun sdkApiFile(project: Project) = File(project.docsDir(), "release/sdk_current.txt")
fun <T : Task> TaskContainer.createWithConfig(
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index cea017c..8a0866b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -37,7 +37,7 @@
val CAR_CLUSTER = Version("1.0.0-alpha6")
val CAR_MODERATOR = Version("1.0.0-alpha1")
val CARDVIEW = Version("1.1.0-alpha01")
- val COLLECTION = Version("1.1.0-rc01")
+ val COLLECTION = Version("1.2.0-alpha01")
val CONTENTPAGER = Version("1.1.0-alpha01")
val COMPOSE = Version("1.0.0-alpha01")
val COORDINATORLAYOUT = Version("1.1.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
deleted file mode 100644
index b191d56..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2016 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 androidx.build.checkapi
-
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.JavaExec
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import java.io.File
-
-/**
- * Task that converts the given API txt file to XML format.
- */
-open class ApiXmlConversionTask : JavaExec() {
- @Optional
- @InputFile
- var inputApiFile: File? = null
-
- @get:OutputFile
- lateinit var outputApiXmlFile: File
-
- init {
- maxHeapSize = "1024m"
-
- // Despite this tool living in ApiCheck, its purpose more fits with doclava's "purposes",
- // generation of api files in this case. Thus, I am putting this in the doclava package.
- main = "com.google.doclava.apicheck.ApiCheck"
- }
-
- override fun exec() {
- val input = inputApiFile
- if (input != null) {
- args = listOf("-convert2xml", input.absolutePath, outputApiXmlFile.absolutePath)
- super.exec()
- } else {
- outputApiXmlFile.delete()
- outputApiXmlFile.writeText("<api>\n</api>")
- }
- }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
index a304e46..f196311 100644
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
@@ -18,221 +18,16 @@
import androidx.build.AndroidXExtension
import androidx.build.Version
-import androidx.build.androidJarFile
-import androidx.build.doclava.CHECK_API_CONFIG_DEVELOP
-import androidx.build.doclava.CHECK_API_CONFIG_PATCH
-import androidx.build.doclava.CHECK_API_CONFIG_RELEASE
-import androidx.build.doclava.ChecksConfig
-import androidx.build.doclava.DoclavaTask
-import androidx.build.docs.ConcatenateFilesTask
-import androidx.build.docsDir
-import androidx.build.jdiff.JDiffTask
-import androidx.build.processProperty
import androidx.build.version
import org.gradle.api.GradleException
import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.TaskProvider
import java.io.File
-data class CheckApiTasks(
- val generateApi: TaskProvider<DoclavaTask>,
- val checkApi: TaskProvider<CheckApiTask>,
- val generateLocalDiffs: TaskProvider<JDiffTask>
-)
-
enum class ApiType {
CLASSAPI,
RESOURCEAPI
}
-/**
- * Sets up api tasks for the given project
- */
-fun initializeApiChecksForProject(
- project: Project,
- aggregateOldApiTxtsTask: TaskProvider<ConcatenateFilesTask>,
- aggregateNewApiTxtsTask: TaskProvider<ConcatenateFilesTask>
-): CheckApiTasks {
- if (!project.hasProperty("docsDir")) {
- project.extensions.add("docsDir", File(project.rootProject.docsDir(), project.name))
- }
- val version = project.version()
-
- val doclavaConfiguration = project.rootProject.configurations.getByName("doclava")
- val docletClasspath = doclavaConfiguration.resolve()
- val generateApi = createGenerateApiTask(project, docletClasspath)
- generateApi.configure {
- it.dependsOn(doclavaConfiguration)
- }
-
- // for verifying that the API surface has not broken since the last minor release
- val lastReleasedApiFile = project.getRequiredCompatibilityApiFile()
-
- val whitelistFile = lastReleasedApiFile?.let { apiFile ->
- File(lastReleasedApiFile.parentFile, stripExtension(apiFile.name) + ".ignore")
- }
- val checkApiRelease = createCheckApiTask(project,
- "checkApiRelease",
- docletClasspath,
- CHECK_API_CONFIG_RELEASE,
- lastReleasedApiFile,
- generateApi.map {
- it.apiFile!!
- },
- whitelistFile)
- checkApiRelease.configure {
- it.dependsOn(generateApi)
- }
-
- // Allow a comma-delimited list of whitelisted errors.
- if (project.hasProperty("ignore")) {
- checkApiRelease.configure {
- it.whitelistErrors = (project.properties["ignore"] as String)
- .split(',').toSet()
- }
- }
-
- // Check whether the development API surface has changed.
- val verifyConfig = if (version.isPatch()) CHECK_API_CONFIG_PATCH else CHECK_API_CONFIG_DEVELOP
- val currentApiFile = project.getCurrentApiFile()
- val checkApi = createCheckApiTask(project,
- "checkApi",
- docletClasspath,
- verifyConfig,
- currentApiFile,
- generateApi.map {
- it.apiFile!!
- },
- null)
- checkApi.configure {
- it.dependsOn(generateApi, checkApiRelease)
- it.group = JavaBasePlugin.VERIFICATION_GROUP
- it.description = "Verify the API surface."
- }
-
- val updateApiTask = createUpdateApiTask(project, checkApiRelease)
- updateApiTask.configure {
- it.dependsOn(checkApiRelease)
- }
-
- val oldApiTxt = getOldApiTxtForDocDiffs(project)
- if (oldApiTxt != null) {
- aggregateOldApiTxtsTask.configure {
- it.addInput(project.name, oldApiTxt)
- }
- }
- val newApiTxtProvider = getNewApiTxt(project, generateApi)
- aggregateNewApiTxtsTask.configure {
- it.inputs.file(newApiTxtProvider.file)
- it.addInput(project.name, newApiTxtProvider.file.get())
- }
-
- val newApiTask = createNewApiXmlTask(project, generateApi, doclavaConfiguration)
- val oldApiTask = createOldApiXml(project, doclavaConfiguration)
-
- val jdiffConfiguration = project.rootProject.configurations.getByName("jdiff")
-
- val generatelocalDiffsTask = createGenerateLocalDiffsTask(project,
- oldApiTask,
- newApiTask,
- jdiffConfiguration)
-
- return CheckApiTasks(generateApi, checkApi, generatelocalDiffsTask)
-}
-
-private fun createGenerateLocalDiffsTask(
- project: Project,
- oldApiTask: TaskProvider<ApiXmlConversionTask>,
- newApiTask: TaskProvider<ApiXmlConversionTask>,
- jdiffConfig: Configuration
-): TaskProvider<JDiffTask> =
- project.tasks.register("generateLocalDiffs", JDiffTask::class.java) {
- it.apply {
- // Base classpath is Android SDK, sub-projects add their own.
- classpath = androidJarFile(project)
-
- // JDiff properties.
- oldApiXmlFile = oldApiTask.get().outputApiXmlFile
- newApiXmlFile = newApiTask.get().outputApiXmlFile
-
- val newApi = project.processProperty("toApi") ?: project.version
- val docsDir = project.rootProject.docsDir()
-
- newJavadocPrefix = "../../../../../reference/"
- destinationDir = File(docsDir,
- "online/sdk/support_api_diff/${project.name}/$newApi")
- // Javadoc properties.
- docletpath = jdiffConfig.resolve()
- title = "AndroidX Library API Differences Report"
-
- exclude("**/BuildConfig.java", "**/R.java")
- dependsOn(oldApiTask, newApiTask, jdiffConfig)
- doFirst {
- println("Generating diffs from api version " +
- "${stripExtension(oldApiTask.get().outputApiXmlFile.name)} " +
- "to api version $newApi")
- }
- }
- }
-
-/**
- * Converts the <code>fromApi</code>.txt file (or the most recently released
- * X.Y.Z.txt if not explicitly defined using -PfromAPi=<file>) to XML format
- * for use by JDiff.
- */
-private fun createOldApiXml(project: Project, doclavaConfig: Configuration) =
- project.tasks.register("oldApiXml", ApiXmlConversionTask::class.java) {
- it.apply {
- val fromApi = project.processProperty("fromApi")
- classpath = project.files(doclavaConfig.resolve())
- val rootFolder = project.projectDir
- inputApiFile = if (fromApi != null) {
- // Use an explicit API file.
- File(rootFolder, "api/$fromApi.txt")
- } else {
- // Use the most recently released API file.
- getLastReleasedApiFile(rootFolder, Version(project.processProperty("toApi")
- ?: project.version.toString()), false, false)
- }
-
- outputApiXmlFile = File(project.docsDir(),
- "release/${stripExtension(inputApiFile?.name ?: "creation")}.xml")
-
- dependsOn(doclavaConfig)
- }
- }
-
-/**
- * Converts the <code>toApi</code>.txt file (or current.txt if not explicitly
- * defined using -PtoApi=<file>) to XML format for use by JDiff.
- */
-private fun createNewApiXmlTask(
- project: Project,
- generateApi: TaskProvider<DoclavaTask>,
- doclavaConfig: Configuration
-) =
- project.tasks.register("newApiXml", ApiXmlConversionTask::class.java) {
- it.apply {
- classpath = project.files(doclavaConfig.resolve())
- val toApi = project.processProperty("toApi")
-
- if (toApi != null && toApi != project.version) {
- // Use an explicit API file.
- inputApiFile = File(project.projectDir, "api/$toApi.txt")
- } else {
- // Use the current API file (e.g. current.txt).
- inputApiFile = generateApi.get().apiFile!!
- dependsOn(generateApi, doclavaConfig)
- }
- // Use either the toApi version, otherwise the most recent version.
- outputApiXmlFile = File(project.docsDir(),
- "release/${toApi ?: project.version}.xml")
- }
- }
-
fun Project.hasApiFolder() = File(projectDir, "api").exists()
fun hasApiTasks(project: Project, extension: AndroidXExtension): Boolean {
@@ -259,53 +54,6 @@
return false
}
-// Creates a new task on the project for generating API files
-private fun createGenerateApiTask(project: Project, docletpathParam: Collection<File>) =
- project.tasks.register("generateApi", DoclavaTask::class.java) {
- it.apply {
- // Base classpath is Android SDK, sub-projects add their own.
- classpath = androidJarFile(project)
- apiFile = File(project.docsDir(), "release/${project.name}/current.txt")
- setDocletpath(docletpathParam)
- destinationDir = project.docsDir()
- generateDocs = false
-
- coreJavadocOptions {
- addBooleanOption("stubsourceonly", true)
- }
-
- exclude("**/R.java")
- }
- }
-
-/**
- * Constructs a new task to copy a generated API file to an appropriately-named "official" API file
- * suitable for source control. This task should be called prior to source control check-in whenever
- * the public API has been modified.
- * <p>
- * The output API file varies according to version:
- * <ul>
- * <li>Snapshot and pre-release versions (e.g. X.Y.Z-SNAPSHOT, X.Y.Z-alphaN) output to current.txt
- * <li>Release versions (e.g. X.Y.Z) output to X.Y.0.txt, throwing an exception if the API has been
- * finalized and the file already exists
- * </ul>
- */
-private fun createUpdateApiTask(project: Project, checkApiRelease: TaskProvider<CheckApiTask>) =
- project.tasks.register("updateApi", UpdateApiTask::class.java) {
- it.apply {
- group = JavaBasePlugin.VERIFICATION_GROUP
- description = "Updates the candidate API file to incorporate valid changes."
- newApiFile = checkApiRelease.get().newApiFile
- oldApiFile = project.getCurrentApiFile()
- whitelistErrors = checkApiRelease.get().whitelistErrors
- whitelistErrorsFile = checkApiRelease.get().whitelistErrorsFile
- doFirst {
- // Replace the expected whitelist with the detected whitelist.
- whitelistErrors = checkApiRelease.get().detectedWhitelistErrors
- }
- }
- }
-
/**
* Returns the API file whose contents match the project's source code.
* This is the API file that the updateApi task will write to.
@@ -362,24 +110,6 @@
return File(apiDir, "${version.major}.${version.minor}.0$extra.txt")
}
-/**
- * Returns the filepath of the previous API txt file
- */
-private fun getOldApiTxtForDocDiffs(project: Project): File? {
- val toApi = project.processProperty("toApi")?.let {
- Version.parseOrNull(it)
- }
- val fromApi = project.processProperty("fromApi")
- val rootFolder = project.projectDir
- return if (fromApi != null) {
- // Use an explicit API file.
- File(rootFolder, "api/$fromApi.txt")
- } else {
- // Use the most recently released API file bounded by toApi.
- getLastReleasedApiFile(rootFolder, toApi, false, false)
- }
-}
-
private fun getLastReleasedApiFile(
rootFolder: File,
refVersion: Version?,
@@ -429,46 +159,3 @@
}
return lastFile
}
-
-// Creates a new task on the project for verifying the API
-private fun createCheckApiTask(
- project: Project,
- taskName: String,
- docletpath: Collection<File>,
- config: ChecksConfig,
- oldApi: File?,
- newApi: Provider<File>,
- whitelist: File? = null
-) =
- project.tasks.register(taskName, CheckApiTask::class.java) {
- it.apply {
- doclavaClasspath = docletpath
- checksConfig = config
- newApiFile = newApi.get()
- oldApiFile = oldApi
- whitelistErrorsFile = whitelist
- doFirst {
- logger.lifecycle("Verifying ${newApi.get().name} " +
- "against ${oldApi?.name ?: "nothing"}...")
- }
- }
- }
-
-private fun getNewApiTxt(project: Project, generateApi: TaskProvider<DoclavaTask>): FileProvider {
- val toApi = project.processProperty("toApi")
- return if (toApi != null) {
- // Use an explicit API file.
- FileProvider(project.provider {
- File(project.projectDir, "api/$toApi.txt")
- }, null)
- } else {
- // Use the current API file (e.g. current.txt).
- FileProvider(generateApi.map {
- it.apiFile!!
- }, generateApi)
- }
-}
-
-private data class FileProvider(val file: Provider<File>, val task: TaskProvider<*>?)
-
-private fun stripExtension(fileName: String) = fileName.substringBeforeLast('.')
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApiTask.kt
deleted file mode 100644
index a183735..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApiTask.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright 2017 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 androidx.build.checkapi
-
-import androidx.build.doclava.ChecksConfig
-import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.security.MessageDigest
-
-/** Character that resets console output color. */
-private const val ANSI_RESET = "\u001B[0m"
-
-/** Character that sets console output color to red. */
-private const val ANSI_RED = "\u001B[31m"
-
-/** Character that sets console output color to yellow. */
-private const val ANSI_YELLOW = "\u001B[33m"
-
-private val ERROR_REGEX = Regex("^(.+):(.+): (\\w+) (\\d+): (.+)$")
-
-private fun ByteArray.encodeHex() = fold(StringBuilder(), { builder, byte ->
- val hexString = Integer.toHexString(byte.toInt() and 0xFF)
- if (hexString.length < 2) {
- builder.append("0")
- }
- builder.append(hexString)
-}).toString()
-
-private fun getShortHash(src: String): String {
- val str = MessageDigest.getInstance("SHA-1")
- .digest(src.toByteArray()).encodeHex()
- val len = str.length
- return str.substring(len - 7, len)
-}
-
-/**
- * Task used to verify changes between two API files.
- * <p>
- * This task may be configured to ignore, warn, or fail with a message for a specific set of
- * Doclava-defined error codes. See {@link com.google.doclava.Errors} for a complete list of
- * supported error codes.
- * <p>
- * Specific failures may be ignored by specifying a list of SHAs in {@link #whitelistErrors}. Each
- * SHA is unique to a specific API change and is logged to the error output on failure.
- */
-open class CheckApiTask : DefaultTask() {
-
- /** API file that represents the existing API surface. */
- @Optional
- @InputFile
- var oldApiFile: File? = null
- get() {
- val result = field
- if (result != null && !result.exists()) {
- throw GradleException("Can't check api against non-existent file $result. " +
- "Please run updateApi to fix that.")
- }
- return result
- }
-
- /** API file that represents the existing API surface's removals. */
- @Optional
- @InputFile
- var oldRemovedApiFile: File? = null
-
- /** API file that represents the candidate API surface. */
- lateinit var newApiFile: File
-
-// defines input. (newApiFile may not exist, so we want to create in this case)
- @Suppress("unused")
- @InputFile
- fun getNewApiFileForInputs(): File {
- if (!newApiFile.exists()) {
- newApiFile.parentFile.mkdirs()
- newApiFile.createNewFile()
- }
- return newApiFile
- }
-
- /** API file that represents the candidate API surface's removals. */
- @Optional
- @InputFile
- var newRemovedApiFile: File? = null
-
- /** Optional file containing a newline-delimited list of error SHAs to ignore. */
- var whitelistErrorsFile: File? = null
-
- @Optional
- @InputFile
- fun getWhiteListErrorsFileInput(): File? {
- // Gradle requires non-null InputFiles to exist -- even with Optional -- so work around that
- // by returning null for this field if the file doesn't exist.
- if (whitelistErrorsFile?.exists() == true) {
- return whitelistErrorsFile
- }
- return null
- }
-
- /**
- * Optional set of error SHAs to ignore.
- * <p>
- * Each error SHA is unique to a specific API change.
- */
- @Optional
- @Input
- var whitelistErrors = emptySet<String>()
-
- var detectedWhitelistErrors = mutableSetOf<String>()
-
- @InputFiles
- var doclavaClasspath: Collection<File> = emptyList()
-
- // A dummy output file meant only to tag when this check was last ran.
- // Without any outputs, Gradle will run this task every time.
- @Optional
- private var mOutputFile: File? = null
-
- @OutputFile
- fun getOutputFile(): File {
- return if (mOutputFile != null) {
- mOutputFile!!
- } else {
- File(project.buildDir, "checkApi/$name-completed")
- }
- }
-
- @Optional
- fun setOutputFile(outputFile: File) {
- mOutputFile = outputFile
- }
-
- @Input
- lateinit var checksConfig: ChecksConfig
-
- init {
- group = "Verification"
- description = "Invoke Doclava\'s ApiCheck tool to make sure current.txt is up to date."
- }
-
- private fun collectAndVerifyInputs(): Set<File> {
- if (oldRemovedApiFile != null && newRemovedApiFile != null) {
- return setOf(oldApiFile!!, newApiFile, oldRemovedApiFile!!, newRemovedApiFile!!)
- } else {
- return setOf(oldApiFile!!, newApiFile)
- }
- }
-
- @TaskAction
- fun exec() {
- if (oldApiFile == null) {
- // Nothing to do.
- return
- }
-
- val apiFiles = collectAndVerifyInputs()
-
- val errStream = ByteArrayOutputStream()
-
- // If either of those gets tweaked, then this should be refactored to extend JavaExec.
- project.javaexec { spec ->
- spec.apply {
- // Put Doclava on the classpath so we can get the ApiCheck class.
- classpath(doclavaClasspath)
- main = "com.google.doclava.apicheck.ApiCheck"
-
- minHeapSize = "128m"
- maxHeapSize = "1024m"
-
- // add -error LEVEL for every error level we want to fail the build on.
- checksConfig.errors.forEach { args("-error", it) }
- checksConfig.warnings.forEach { args("-warning", it) }
- checksConfig.hidden.forEach { args("-hide", it) }
-
- spec.args(apiFiles.map { it.absolutePath })
-
- // Redirect error output so that we can whitelist specific errors.
- errorOutput = errStream
- // We will be handling failures ourselves with a custom message.
- setIgnoreExitValue(true)
- }
- }
-
- // Load the whitelist file, if present.
- val whitelistFile = whitelistErrorsFile
- if (whitelistFile?.exists() == true) {
- whitelistErrors += whitelistFile.readLines()
- }
-
- // Parse the error output.
- val unparsedErrors = mutableSetOf<String>()
- val detectedErrors = mutableSetOf<List<String>>()
- val parsedErrors = mutableSetOf<List<String>>()
- ByteArrayInputStream(errStream.toByteArray()).bufferedReader().lines().forEach {
- val match = ERROR_REGEX.matchEntire(it)
-
- if (match == null) {
- unparsedErrors.add(it)
- } else if (match.groups[3]?.value == "error") {
- val hash = getShortHash(match.groups[5]?.value!!)
- val error = match.groupValues.subList(1, match.groupValues.size) + listOf(hash)
- if (hash in whitelistErrors) {
- detectedErrors.add(error)
- detectedWhitelistErrors.add(error[5])
- } else {
- parsedErrors.add(error)
- }
- }
- }
-
- unparsedErrors.forEach { error -> logger.error("$ANSI_RED$error$ANSI_RESET") }
- parsedErrors.forEach { logger.error("$ANSI_RED${it[5]}$ANSI_RESET ${it[4]}") }
- detectedErrors.forEach { logger.warn("$ANSI_YELLOW${it[5]}$ANSI_RESET ${it[4]}") }
-
- if (unparsedErrors.isNotEmpty() || parsedErrors.isNotEmpty()) {
- throw GradleException(checksConfig.onFailMessage ?: "")
- }
-
- // Just create a dummy file upon completion. Without any outputs, Gradle will run this task
- // every time.
- val outputFile = getOutputFile()
- outputFile.parentFile.mkdirs()
- outputFile.createNewFile()
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/UpdateApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/UpdateApiTask.kt
deleted file mode 100644
index 26a0e6a..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/UpdateApiTask.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 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 androidx.build.checkapi
-
-import androidx.build.Version
-import com.google.common.io.Files
-import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-import java.nio.charset.Charset
-import java.util.HashSet
-
-/**
- * Task for updating the checked in API file with the newly generated one.
- */
-open class UpdateApiTask : DefaultTask() {
- @get:InputFile
- lateinit var newApiFile: File
- @get:[InputFile Optional]
- var newRemovedApiFile: File? = null
-
- @get:[Input Optional]
- var whitelistErrors: Set<String> = HashSet()
-
- @get:OutputFile
- lateinit var oldApiFile: File
- @get:[OutputFile Optional]
- var oldRemovedApiFile: File? = null
-
- @get:[OutputFile Optional]
- var whitelistErrorsFile: File? = null
-
- /**
- * Actually copy the file to the desired location and update the whitelist warnings file.
- */
- @TaskAction
- fun doUpdate() {
- if (oldApiFile.exists() && newApiFile.readText() == oldApiFile.readText()) {
- // whatever nothing changed
- return
- }
-
- val version = Version(project.version as String)
- if (version.isPatch()) {
- throw GradleException("Public APIs may not be modified in patch releases.")
- } else if (version.isFinalApi() && oldApiFile.exists() && !project.hasProperty("force")) {
- throw GradleException("Public APIs may not be modified in finalized releases.")
- }
-
- Files.copy(newApiFile, oldApiFile)
-
- if (oldApiFile.name != "current.txt") {
- Files.copy(newApiFile, File(project.projectDir, "api/current.txt"))
- }
-
- if (oldRemovedApiFile != null) {
- if (newRemovedApiFile != null) {
- Files.copy(newRemovedApiFile!!, oldRemovedApiFile!!)
- } else {
- oldRemovedApiFile!!.delete()
- }
- }
-
- if (whitelistErrorsFile != null && !whitelistErrors.isEmpty()) {
- Files.newWriter(
- whitelistErrorsFile!!, Charset.defaultCharset()).use { writer ->
- for (error in whitelistErrors) {
- writer.write(error + "\n")
- }
- }
- logger.lifecycle("Whitelisted " + whitelistErrors.size + " error(s)...")
- }
-
- logger.lifecycle("Wrote public API definition to " + oldApiFile.name)
- }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index e9021ae..a3404b7 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -46,10 +46,10 @@
const val JUNIT = "junit:junit:4.12"
const val KOTLINPOET = "com.squareup:kotlinpoet:1.1.0"
-private const val KOTLIN_VERSION = "1.3.20"
+private const val KOTLIN_VERSION = "1.3.31"
const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
const val KOTLIN_TEST_COMMON = "org.jetbrains.kotlin:kotlin-test:$KOTLIN_VERSION"
-const val COMPOSE_VERSION="1.3.30-compose-20190503"
+const val COMPOSE_VERSION = "1.3.30-compose-20190503"
const val KOTLIN_COMPOSE_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:$COMPOSE_VERSION"
const val KOTLIN_COMPOSE_REFLECT = "org.jetbrains.kotlin:kotlin-reflect:$COMPOSE_VERSION"
diff --git a/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt b/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
deleted file mode 100644
index 54e9ec6..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2016 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 androidx.build.jdiff
-
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.javadoc.Javadoc
-import org.gradle.external.javadoc.CoreJavadocOptions
-import org.gradle.external.javadoc.MinimalJavadocOptions
-
-import java.io.File
-import java.util.ArrayList
-
-/**
- * JDiff task to compare API changes.
- */
-open class JDiffTask : Javadoc() {
-
- /**
- * Sets the doclet path, which will be used to locate the `com.google.doclava.Doclava` class.
- *
- *
- * This option will override any doclet path set in this instance's
- * [JavadocOptions][.getOptions].
- *
- * @see MinimalJavadocOptions.setDocletpath
- */
- @get:InputFiles
- var docletpath: Collection<File>? = null
- set(docletpath) {
- field = docletpath
- // Go ahead and keep the mDocletpath in our JavadocOptions object in sync.
- options.docletpath = ArrayList(docletpath)
- }
-
- @get:InputFile
- lateinit var oldApiXmlFile: File
-
- @get:InputFile
- lateinit var newApiXmlFile: File
-
- /**
- * Relative path to the Javadoc corresponding to the old API, relative to
- * "${destinationDir}/changes". Should end with the directory separator (usually '/').
- */
- @get:[Input Optional]
- var oldJavadocPrefix: String? = null
-
- /**
- * Relative path to the Javadoc corresponding to the new API, relative to
- * "${destinationDir}/changes". Should end with the directory separator (usually '/').
- */
- @get:[Input Optional]
- var newJavadocPrefix: String? = null
-
- // HTML diff files will be placed in destinationDir, which is defined by the superclass.
-
- @get:Input
- var stats = true
-
- init {
- isFailOnError = true
- options.doclet = "jdiff.JDiff"
- options.encoding = "UTF-8"
- maxMemory = "1280m"
- }
-
- /**
- * Configures this JDiffTask with parameters that might not be at their final values
- * until this task is run.
- */
- private fun configureJDiffTask() {
- val options = options as CoreJavadocOptions
-
- options.docletpath = ArrayList(docletpath!!)
-
- if (stats) {
- options.addStringOption("stats")
- }
-
- val oldApiXmlFileDir = oldApiXmlFile.parentFile
- val newApiXmlFileDir = newApiXmlFile.parentFile
-
- if (oldApiXmlFileDir.exists()) {
- options.addStringOption("oldapidir", oldApiXmlFileDir.absolutePath)
- }
- // For whatever reason, jdiff appends .xml to the file name on its own.
- // Strip the .xml off the end of the file name
- options.addStringOption("oldapi",
- oldApiXmlFile.name.substring(0, oldApiXmlFile.name.length - 4))
- if (newApiXmlFileDir.exists()) {
- options.addStringOption("newapidir", newApiXmlFileDir.absolutePath)
- }
- options.addStringOption("newapi",
- newApiXmlFile.name.substring(0, newApiXmlFile.name.length - 4))
-
- if (oldJavadocPrefix != null) {
- options.addStringOption("javadocold", oldJavadocPrefix)
- }
- if (newJavadocPrefix != null) {
- options.addStringOption("javadocnew", newJavadocPrefix)
- }
- }
-
- public override fun generate() {
- configureJDiffTask()
- super.generate()
- }
-}
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt b/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
index b6c9d97..ca23588 100644
--- a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
+++ b/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/BuildPropParserTest.kt
@@ -37,7 +37,6 @@
"""
platform/external/doclava 798edee4d01239248ed33585a42b19ca93c31f56
platform/external/flatbuffers b5c9b8e12b47d0597cd29b217e5765d325957d8a
- platform/external/jdiff fe19ba361e4a1230108378b4634784281dd9d250
platform/external/noto-fonts d20a289eaebf2e371064dacc306b57d37b733f7c
platform/frameworks/support fc9030edfe7e0a85a5545a342c7efbf93283f62f
platform/manifest 446c4307abb8d676b308af55b06e1d1a01c858c7
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/Camera2Config.java b/camera/camera2/src/main/java/androidx/camera/camera2/Camera2Config.java
index 39c4ce8..f0e4917 100644
--- a/camera/camera2/src/main/java/androidx/camera/camera2/Camera2Config.java
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/Camera2Config.java
@@ -27,6 +27,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
+import androidx.camera.camera2.impl.CameraEventCallbacks;
import androidx.camera.core.Config;
import androidx.camera.core.MutableConfig;
import androidx.camera.core.MutableOptionsBundle;
@@ -55,6 +56,11 @@
CameraCaptureSession.StateCallback.class);
static final Option<CaptureCallback> SESSION_CAPTURE_CALLBACK_OPTION =
Option.create("camera2.cameraCaptureSession.captureCallback", CaptureCallback.class);
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY)
+ public static final Option<CameraEventCallbacks> CAMERA_EVENT_CALLBACK_OPTION =
+ Option.create("camera2.cameraEvent.callback", CameraEventCallbacks.class);
// *********************************************************************************************
private final Config mConfig;
@@ -163,6 +169,17 @@
return mConfig.retrieveOption(SESSION_CAPTURE_CALLBACK_OPTION, valueIfMissing);
}
+ /**
+ * Returns the stored CameraEventCallbacks instance.
+ *
+ * @param valueIfMissing The value to return if this configuration option has not been set.
+ * @return The stored value or <code>valueIfMissing</code> if the value does not exist in this
+ * configuration.
+ */
+ public CameraEventCallbacks getCameraEventCallback(CameraEventCallbacks valueIfMissing) {
+ return mConfig.retrieveOption(CAMERA_EVENT_CALLBACK_OPTION, valueIfMissing);
+ }
+
// Start of the default implementation of Config
// *********************************************************************************************
@@ -321,6 +338,18 @@
captureCallback);
return this;
}
+
+ /**
+ * Sets a CameraEventCallbacks instance.
+ *
+ * @param cameraEventCallbacks The CameraEventCallbacks.
+ * @return The current Extender.
+ */
+ public Extender setCameraEventCallback(CameraEventCallbacks cameraEventCallbacks) {
+ mBaseBuilder.getMutableConfig().insertOption(CAMERA_EVENT_CALLBACK_OPTION,
+ cameraEventCallbacks);
+ return this;
+ }
}
/**
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
index 9498a3e..0ecf8f8 100644
--- a/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
@@ -254,6 +254,7 @@
void closeCameraResource() {
mCaptureSession.close();
+ mCaptureSession.release();
mCameraDevice.close();
notifyCameraDeviceCloseToCaptureSessions();
mCameraDevice = null;
@@ -623,6 +624,7 @@
SessionConfig previousSessionConfig = mCaptureSession.getSessionConfig();
mCaptureSession.close();
+ mCaptureSession.release();
// Saves the closed CaptureSessions if device is not closed yet.
// We need to notify camera device closed event to these CaptureSessions.
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera2OptionUnpacker.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera2OptionUnpacker.java
index f76f343..45e0351 100644
--- a/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera2OptionUnpacker.java
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/Camera2OptionUnpacker.java
@@ -23,6 +23,7 @@
import androidx.camera.core.CameraDeviceStateCallbacks;
import androidx.camera.core.Config;
import androidx.camera.core.Config.Option;
+import androidx.camera.core.MutableOptionsBundle;
import androidx.camera.core.OptionsBundle;
import androidx.camera.core.SessionConfig;
import androidx.camera.core.UseCaseConfig;
@@ -77,6 +78,11 @@
camera2Config.getSessionCaptureCallback(
Camera2CaptureCallbacks.createNoOpCallback())));
+ MutableOptionsBundle cameraEventConfig = MutableOptionsBundle.create();
+ cameraEventConfig.insertOption(Camera2Config.CAMERA_EVENT_CALLBACK_OPTION,
+ camera2Config.getCameraEventCallback(CameraEventCallbacks.createEmptyCallback()));
+ builder.addImplementationOptions(cameraEventConfig);
+
// Copy extension keys
camera2Config.findOptions(
Camera2Config.CAPTURE_REQUEST_ID_STEM,
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallback.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallback.java
new file mode 100644
index 0000000..e364e9d
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallback.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 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 androidx.camera.camera2.impl;
+
+import android.hardware.camera2.CameraCaptureSession;
+
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.CaptureConfig;
+
+/**
+ * A callback object for tracking the camera capture session event and get request data.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class CameraEventCallback {
+
+ /**
+ * This will be invoked before creating a {@link CameraCaptureSession}. The returned
+ * parameter in CaptureConfig will be passed to the camera device as part of the capture session
+ * initialization via setSessionParameters(). The valid parameter is a subset of the
+ * available capture request parameters.
+ *
+ * @return CaptureConfig The request information to customize the session.
+ */
+ public CaptureConfig onPresetSession() {
+ return null;
+ }
+
+ /**
+ * This will be invoked once after a {@link CameraCaptureSession} is created. The returned
+ * parameter in CaptureConfig will be used to generate a single request to the current
+ * configured camera device. The generated request would be submitted to camera before process
+ * other single request.
+ *
+ * @return CaptureConfig The request information to customize the session.
+ */
+ public CaptureConfig onEnableSession() {
+ return null;
+ }
+
+ /**
+ * This callback will be invoked before starting the repeating request in the
+ * {@link CameraCaptureSession}. The returned CaptureConfig will be used to generate a
+ * capture request, and would be used in setRepeatingRequest().
+ *
+ * @return CaptureConfig The request information to customize the session.
+ */
+ public CaptureConfig onRepeating() {
+ return null;
+ }
+
+ /**
+ * This will be invoked once before the {@link CameraCaptureSession} is closed. The
+ * returned parameter in CaptureConfig will be used to generate a single request to the current
+ * configured camera device. The generated request would be submitted to camera before the
+ * capture session was closed.
+ *
+ * @return CaptureConfig The request information to customize the session.
+ */
+ public CaptureConfig onDisableSession() {
+ return null;
+ }
+
+}
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java
new file mode 100644
index 0000000..a5187c8d
--- /dev/null
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2019 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 androidx.camera.camera2.impl;
+
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.CaptureConfig;
+import androidx.camera.core.MultiValueSet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Different implementations of {@link CameraEventCallback}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public final class CameraEventCallbacks extends MultiValueSet<CameraEventCallback> {
+
+ public CameraEventCallbacks(CameraEventCallback ... callbacks) {
+ addAll(Arrays.asList(callbacks));
+ }
+
+ /** Returns a camera event callback which calls a list of other callbacks. */
+ public ComboCameraEventCallback createComboCallback() {
+ return new ComboCameraEventCallback(getAllItems());
+ }
+
+ /** Returns a camera event callback which does nothing. */
+ public static CameraEventCallbacks createEmptyCallback() {
+ return new CameraEventCallbacks();
+ }
+
+ @Override
+ public MultiValueSet<CameraEventCallback> clone() {
+ CameraEventCallbacks ret = createEmptyCallback();
+ ret.addAll(getAllItems());
+ return ret;
+ }
+
+ /**
+ * A CameraEventCallback which contains a list of CameraEventCallback and will
+ * propagate received callback to the list.
+ */
+ public static final class ComboCameraEventCallback {
+ private final List<CameraEventCallback> mCallbacks = new ArrayList<>();
+
+ ComboCameraEventCallback(List<CameraEventCallback> callbacks) {
+ for (CameraEventCallback callback : callbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * To Invoke {@link CameraEventCallback#onPresetSession()} on the set of list and
+ * aggregated the results to a set list.
+ *
+ * @return List<CaptureConfig> The request information to customize the session.
+ */
+ public List<CaptureConfig> onPresetSession() {
+ List<CaptureConfig> ret = new LinkedList<>();
+ for (CameraEventCallback callback : mCallbacks) {
+ CaptureConfig presetCaptureStage = callback.onPresetSession();
+ if (presetCaptureStage != null) {
+ ret.add(presetCaptureStage);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * To Invoke {@link CameraEventCallback#onEnableSession()} on the set of list and
+ * aggregated the results to a set list.
+ *
+ * @return List<CaptureConfig> The request information to customize the session.
+ */
+ public List<CaptureConfig> onEnableSession() {
+ List<CaptureConfig> ret = new LinkedList<>();
+ for (CameraEventCallback callback : mCallbacks) {
+ CaptureConfig enableCaptureStage = callback.onEnableSession();
+ if (enableCaptureStage != null) {
+ ret.add(enableCaptureStage);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * To Invoke {@link CameraEventCallback#onRepeating()} on the set of list and
+ * aggregated the results to a set list.
+ *
+ * @return List<CaptureConfig> The request information to customize the session.
+ */
+ public List<CaptureConfig> onRepeating() {
+ List<CaptureConfig> ret = new LinkedList<>();
+ for (CameraEventCallback callback : mCallbacks) {
+ CaptureConfig repeatingCaptureStage = callback.onRepeating();
+ if (repeatingCaptureStage != null) {
+ ret.add(repeatingCaptureStage);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * To Invoke {@link CameraEventCallback#onDisableSession()} on the set of list and
+ * aggregated the results to a set list.
+ *
+ * @return List<CaptureConfig> The request information to customize the session.
+ */
+ public List<CaptureConfig> onDisableSession() {
+ List<CaptureConfig> ret = new LinkedList<>();
+ for (CameraEventCallback callback : mCallbacks) {
+ CaptureConfig disableCaptureStage = callback.onDisableSession();
+ if (disableCaptureStage != null) {
+ ret.add(disableCaptureStage);
+ }
+ }
+ return ret;
+ }
+
+ @NonNull
+ public List<CameraEventCallback> getCallbacks() {
+ return mCallbacks;
+ }
+ }
+}
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
index 388efbc..d8fe2bc 100644
--- a/camera/camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
@@ -22,7 +22,11 @@
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.os.Build;
import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.view.Surface;
@@ -36,11 +40,16 @@
import androidx.camera.core.Config.Option;
import androidx.camera.core.DeferrableSurface;
import androidx.camera.core.DeferrableSurfaces;
+import androidx.camera.core.ImmediateSurface;
import androidx.camera.core.SessionConfig;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
/**
* A session for capturing images from the camera which is tied to a specific {@link CameraDevice}.
@@ -54,6 +63,8 @@
/** Handler for all the callbacks from the {@link CameraCaptureSession}. */
@Nullable
private final Handler mHandler;
+ /** An adapter to pass the task to the handler. */
+ private final Executor mExecutor;
/** The configuration for the currently issued single capture requests. */
private final List<CaptureConfig> mCaptureConfigs = new ArrayList<>();
/** Lock on whether the camera is open or closed. */
@@ -80,6 +91,8 @@
/** The list of DeferrableSurface used to notify surface detach events */
@GuardedBy("mConfiguredDeferrableSurfaces")
List<DeferrableSurface> mConfiguredDeferrableSurfaces = Collections.emptyList();
+ /** The list of DeferrableSurface used for repeating request */
+ private List<DeferrableSurface> mConfiguredRepeatingSurfaces = Collections.emptyList();
/** Tracks the current state of the session. */
@GuardedBy("mStateLock")
State mState = State.UNINITIALIZED;
@@ -95,6 +108,7 @@
CaptureSession(@Nullable Handler handler) {
this.mHandler = handler;
mState = State.INITIALIZED;
+ mExecutor = new ExecutorHandlerAdapter(handler);
}
/**
@@ -185,6 +199,15 @@
return;
}
+ // TODO(b/132664086): Remove this workaround after aosp/955625 has been
+ // submitted.
+ Set<Surface> repeatingSurfaces = DeferrableSurfaces.surfaceSet(
+ sessionConfig.getRepeatingCaptureConfig().getSurfaces());
+ mConfiguredRepeatingSurfaces = new ArrayList<>();
+ for (Surface s : repeatingSurfaces) {
+ mConfiguredRepeatingSurfaces.add(new ImmediateSurface(s));
+ }
+
notifySurfaceAttached();
mState = State.OPENING;
Log.d(TAG, "Opening capture session.");
@@ -193,7 +216,42 @@
callbacks.add(mCaptureSessionStateCallback);
CameraCaptureSession.StateCallback comboCallback =
CameraCaptureSessionStateCallbacks.createComboCallback(callbacks);
- cameraDevice.createCaptureSession(mConfiguredSurfaces, comboCallback, mHandler);
+
+ // Start check preset CaptureStage information.
+ CameraEventCallbacks eventCallbacks = new Camera2Config(
+ sessionConfig.getImplementationOptions()).getCameraEventCallback(
+ CameraEventCallbacks.createEmptyCallback());
+ List<CaptureConfig> presetList =
+ eventCallbacks.createComboCallback().onPresetSession();
+
+ // Generate the CaptureRequest builder from repeating request since Android
+ // recommend use the same template type as the initial capture request. The
+ // tag and output targets would be ignored by default.
+ CaptureRequest.Builder builder =
+ sessionConfig.getRepeatingCaptureConfig().buildCaptureRequestNoTarget(
+ cameraDevice);
+
+ if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P
+ && builder != null && !presetList.isEmpty()) {
+ for (CaptureConfig config : presetList) {
+ applyImplementationOptionTCaptureBuilder(builder,
+ config.getImplementationOptions());
+ }
+
+ List<OutputConfiguration> outputConfigList = new LinkedList<>();
+ for (Surface surface : mConfiguredSurfaces) {
+ outputConfigList.add(new OutputConfiguration(surface));
+ }
+
+ SessionConfiguration sessionParameterConfiguration =
+ new SessionConfiguration(SessionConfiguration.SESSION_REGULAR,
+ outputConfigList, mExecutor, comboCallback);
+ sessionParameterConfiguration.setSessionParameters(builder.build());
+ cameraDevice.createCaptureSession(sessionParameterConfiguration);
+ } else {
+ cameraDevice.createCaptureSession(mConfiguredSurfaces, comboCallback,
+ mHandler);
+ }
break;
default:
Log.e(TAG, "Open not allowed in state: " + mState);
@@ -219,8 +277,20 @@
case INITIALIZED:
mState = State.RELEASED;
break;
- case OPENING:
case OPENED:
+ // Only issue onDisableSession requests at OPENED state.
+ if (mSessionConfig != null) {
+ CameraEventCallbacks eventCallbacks = new Camera2Config(
+ mSessionConfig.getImplementationOptions()).getCameraEventCallback(
+ CameraEventCallbacks.createEmptyCallback());
+ List<CaptureConfig> configList =
+ eventCallbacks.createComboCallback().onDisableSession();
+ if (!configList.isEmpty()) {
+ issueCaptureRequests(setupConfiguredSurface(configList));
+ }
+ }
+ // Not break close flow.
+ case OPENING:
mState = State.CLOSED;
mSessionConfig = null;
break;
@@ -255,7 +325,9 @@
break;
case OPENED:
case CLOSED:
- mCameraCaptureSession.close();
+ if (mCameraCaptureSession != null) {
+ mCameraCaptureSession.close();
+ }
mState = State.RELEASING;
break;
case RELEASING:
@@ -350,6 +422,16 @@
return;
}
+ CameraEventCallbacks eventCallbacks = new Camera2Config(
+ mSessionConfig.getImplementationOptions()).getCameraEventCallback(
+ CameraEventCallbacks.createEmptyCallback());
+ List<CaptureConfig> repeatingRequestList =
+ eventCallbacks.createComboCallback().onRepeating();
+ for (CaptureConfig config : repeatingRequestList) {
+ applyImplementationOptionTCaptureBuilder(builder,
+ config.getImplementationOptions());
+ }
+
applyImplementationOptionTCaptureBuilder(
builder, captureConfig.getImplementationOptions());
@@ -504,6 +586,20 @@
case OPENING:
mState = State.OPENED;
mCameraCaptureSession = session;
+
+ // Issue capture request of enableSession if exists.
+ if (mSessionConfig != null) {
+ Config implOptions = mSessionConfig.getImplementationOptions();
+ CameraEventCallbacks eventCallbacks = new Camera2Config(
+ implOptions).getCameraEventCallback(
+ CameraEventCallbacks.createEmptyCallback());
+ List<CaptureConfig> list =
+ eventCallbacks.createComboCallback().onEnableSession();
+ if (!list.isEmpty()) {
+ issueCaptureRequests(setupConfiguredSurface(list));
+ }
+ }
+
Log.d(TAG, "Attempting to send capture request onConfigured");
issueRepeatingCaptureRequests();
issueBurstCaptureRequest();
@@ -578,4 +674,47 @@
public void notifyCameraDeviceClose() {
notifySurfaceDetached();
}
+
+ List<CaptureConfig> setupConfiguredSurface(List<CaptureConfig> list) {
+ List<CaptureConfig> ret = new ArrayList<>();
+ for (CaptureConfig c : list) {
+ CaptureConfig.Builder builder = CaptureConfig.Builder.from(c);
+ builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
+ for (DeferrableSurface s : mConfiguredRepeatingSurfaces) {
+ builder.addSurface(s);
+ }
+ ret.add(builder.build());
+ }
+
+ return ret;
+ }
+
+ private static class ExecutorHandlerAdapter implements Executor {
+
+ private final Handler mHandler;
+ ExecutorHandlerAdapter(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ Handler handler;
+
+ if (mHandler == null) {
+ // Run in current thread when handler not exist.
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalArgumentException(
+ "No handler given, and current thread has no looper!");
+ }
+ handler = new Handler(looper);
+ } else {
+ handler = mHandler;
+ }
+
+ if (!handler.post(command)) {
+ throw new RejectedExecutionException(handler + " is shutting down");
+ }
+ }
+ }
}
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/FakeOtherUseCaseConfig.java b/camera/core/src/androidTest/java/androidx/camera/core/FakeOtherUseCaseConfig.java
index d1cf114..2798b0f 100644
--- a/camera/core/src/androidTest/java/androidx/camera/core/FakeOtherUseCaseConfig.java
+++ b/camera/core/src/androidTest/java/androidx/camera/core/FakeOtherUseCaseConfig.java
@@ -163,6 +163,23 @@
return retrieveOption(OPTION_SURFACE_OCCUPANCY_PRIORITY);
}
+ /** @hide */
+ @Nullable
+ @Override
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public UseCase.EventListener getUseCaseEventListener(
+ @Nullable UseCase.EventListener valueIfMissing) {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER, valueIfMissing);
+ }
+
+ /** @hide */
+ @Nullable
+ @Override
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public UseCase.EventListener getUseCaseEventListener() {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER);
+ }
+
// End of the default implementation of Config
// *********************************************************************************************
@@ -245,5 +262,13 @@
getMutableConfig().insertOption(OPTION_SURFACE_OCCUPANCY_PRIORITY, priority);
return this;
}
+
+ /** @hide */
+ @Override
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public Builder setUseCaseEventListener(UseCase.EventListener eventListener) {
+ getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_LISTENER, eventListener);
+ return this;
+ }
}
}
diff --git a/camera/core/src/main/java/androidx/camera/core/CaptureConfig.java b/camera/core/src/main/java/androidx/camera/core/CaptureConfig.java
index 6e89a7b..9bc8547 100644
--- a/camera/core/src/main/java/androidx/camera/core/CaptureConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/CaptureConfig.java
@@ -166,6 +166,31 @@
}
/**
+ * TODO(b/132664086): To replace this old implementation by Camera2CaptureRequestBuilder once
+ * aosp/955625 was submitted.
+ *
+ * Return the builder of a {@link CaptureRequest} which include capture request parameters and
+ * desired template type, but no target surfaces and tag.
+ *
+ * <p>Returns {@code null} if a valid {@link CaptureRequest} can not be constructed.
+ */
+ @Nullable
+ public CaptureRequest.Builder buildCaptureRequestNoTarget(@Nullable CameraDevice device)
+ throws CameraAccessException {
+ if (device == null) {
+ return null;
+ }
+ CaptureRequest.Builder builder = device.createCaptureRequest(mTemplateType);
+
+ for (CaptureRequestParameter<?> captureRequestParameter :
+ mCaptureRequestParameters.values()) {
+ captureRequestParameter.apply(builder);
+ }
+
+ return builder;
+ }
+
+ /**
* Builder for easy modification/rebuilding of a {@link CaptureConfig}.
*
* @hide
@@ -274,7 +299,17 @@
for (Option<?> option : config.listOptions()) {
@SuppressWarnings("unchecked") // Options/values are being copied directly
Option<Object> objectOpt = (Option<Object>) option;
- mImplementationOptions.insertOption(objectOpt, config.retrieveOption(objectOpt));
+
+ Object existValue = mImplementationOptions.retrieveOption(objectOpt, null);
+ Object newValue = config.retrieveOption(objectOpt);
+ if (existValue instanceof MultiValueSet) {
+ ((MultiValueSet) existValue).addAll(((MultiValueSet) newValue).getAllItems());
+ } else {
+ if (newValue instanceof MultiValueSet) {
+ newValue = ((MultiValueSet) newValue).clone();
+ }
+ mImplementationOptions.insertOption(objectOpt, newValue);
+ }
}
}
diff --git a/camera/core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java b/camera/core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
index 849b9a7..63324b9 100644
--- a/camera/core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/ImageAnalysisConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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.
@@ -411,15 +411,32 @@
return retrieveOption(OPTION_SURFACE_OCCUPANCY_PRIORITY);
}
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener(
+ @Nullable UseCase.EventListener valueIfMissing) {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER, valueIfMissing);
+ }
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener() {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER);
+ }
+
// End of the default implementation of Config
// *********************************************************************************************
/** Builder for a {@link ImageAnalysisConfig}. */
public static final class Builder
- implements CameraDeviceConfig.Builder<ImageAnalysisConfig.Builder>,
- ImageOutputConfig.Builder<ImageAnalysisConfig.Builder>,
- ThreadConfig.Builder<ImageAnalysisConfig.Builder>,
- UseCaseConfig.Builder<ImageAnalysis, ImageAnalysisConfig, ImageAnalysisConfig.Builder> {
+ implements CameraDeviceConfig.Builder<Builder>,
+ ImageOutputConfig.Builder<Builder>,
+ ThreadConfig.Builder<Builder>,
+ UseCaseConfig.Builder<ImageAnalysis, ImageAnalysisConfig, Builder> {
private final MutableOptionsBundle mMutableConfig;
@@ -666,5 +683,13 @@
getMutableConfig().insertOption(OPTION_SURFACE_OCCUPANCY_PRIORITY, priority);
return this;
}
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Override
+ public Builder setUseCaseEventListener(UseCase.EventListener useCaseEventListener) {
+ getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_LISTENER, useCaseEventListener);
+ return this;
+ }
}
}
diff --git a/camera/core/src/main/java/androidx/camera/core/ImageCaptureConfig.java b/camera/core/src/main/java/androidx/camera/core/ImageCaptureConfig.java
index 8453aa3..237b27d8 100644
--- a/camera/core/src/main/java/androidx/camera/core/ImageCaptureConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/ImageCaptureConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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.
@@ -40,9 +40,9 @@
// Option Declarations:
// *********************************************************************************************
- static final Option<ImageCapture.CaptureMode> OPTION_IMAGE_CAPTURE_MODE =
+ static final Option<CaptureMode> OPTION_IMAGE_CAPTURE_MODE =
Option.create(
- "camerax.core.imageCapture.captureMode", ImageCapture.CaptureMode.class);
+ "camerax.core.imageCapture.captureMode", CaptureMode.class);
static final Option<FlashMode> OPTION_FLASH_MODE =
Option.create("camerax.core.imageCapture.flashMode", FlashMode.class);
static final Option<CaptureBundle> OPTION_CAPTURE_BUNDLE =
@@ -51,6 +51,8 @@
Option.create("camerax.core.imageCapture.captureProcessor", CaptureProcessor.class);
static final Option<Integer> OPTION_BUFFER_FORMAT =
Option.create("camerax.core.imageCapture.bufferFormat", Integer.class);
+ static final Option<Integer> OPTION_MAX_CAPTURE_STAGES =
+ Option.create("camerax.core.imageCapture.maxCaptureStages", Integer.class);
// *********************************************************************************************
@@ -62,25 +64,25 @@
}
/**
- * Returns the {@link ImageCapture.CaptureMode}.
+ * Returns the {@link CaptureMode}.
*
* @param valueIfMissing The value to return if this configuration option has not been set.
* @return The stored value or <code>valueIfMissing</code> if the value does not exist in this
* configuration.
*/
@Nullable
- public ImageCapture.CaptureMode getCaptureMode(
- @Nullable ImageCapture.CaptureMode valueIfMissing) {
+ public CaptureMode getCaptureMode(
+ @Nullable CaptureMode valueIfMissing) {
return retrieveOption(OPTION_IMAGE_CAPTURE_MODE, valueIfMissing);
}
/**
- * Returns the {@link ImageCapture.CaptureMode}.
+ * Returns the {@link CaptureMode}.
*
* @return The stored value, if it exists in this configuration.
* @throws IllegalArgumentException if the option does not exist in this configuration.
*/
- public ImageCapture.CaptureMode getCaptureMode() {
+ public CaptureMode getCaptureMode() {
return retrieveOption(OPTION_IMAGE_CAPTURE_MODE);
}
@@ -185,6 +187,32 @@
return retrieveOption(OPTION_BUFFER_FORMAT);
}
+ /**
+ * Returns the max number of {@link CaptureStage}.
+ *
+ * @param valueIfMissing The value to return if this configuration option has not been set.
+ * @return The stored value or <code>valueIfMissing</code> if the value does not exist in
+ * this configuration.
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ public int getMaxCaptureStages(int valueIfMissing) {
+ return retrieveOption(OPTION_MAX_CAPTURE_STAGES, valueIfMissing);
+ }
+
+ /**
+ * Returns the max number of {@link CaptureStage}.
+ *
+ * @return The stored value, if it exists in this configuration.
+ * @throws IllegalArgumentException if the option does not exist in this configuration.
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public int getMaxCaptureStages() {
+ return retrieveOption(OPTION_MAX_CAPTURE_STAGES);
+ }
+
// Start of the default implementation of Config
// *********************************************************************************************
@@ -486,16 +514,33 @@
return retrieveOption(OPTION_SURFACE_OCCUPANCY_PRIORITY);
}
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener(
+ @Nullable UseCase.EventListener valueIfMissing) {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER, valueIfMissing);
+ }
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener() {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER);
+ }
+
// End of the default implementation of Config
// *********************************************************************************************
/** Builder for a {@link ImageCaptureConfig}. */
public static final class Builder
implements UseCaseConfig.Builder<
- ImageCapture, ImageCaptureConfig, ImageCaptureConfig.Builder>,
- ImageOutputConfig.Builder<ImageCaptureConfig.Builder>,
- CameraDeviceConfig.Builder<ImageCaptureConfig.Builder>,
- ThreadConfig.Builder<ImageCaptureConfig.Builder> {
+ ImageCapture, ImageCaptureConfig, Builder>,
+ ImageOutputConfig.Builder<Builder>,
+ CameraDeviceConfig.Builder<Builder>,
+ ThreadConfig.Builder<Builder> {
private final MutableOptionsBundle mMutableConfig;
@@ -560,7 +605,7 @@
* @param captureMode The requested image capture mode.
* @return The current Builder.
*/
- public Builder setCaptureMode(ImageCapture.CaptureMode captureMode) {
+ public Builder setCaptureMode(CaptureMode captureMode) {
getMutableConfig().insertOption(OPTION_IMAGE_CAPTURE_MODE, captureMode);
return this;
}
@@ -622,6 +667,19 @@
return this;
}
+ /**
+ * Sets the max number of {@link CaptureStage}.
+ *
+ * @param maxCaptureStages The max CaptureStage number.
+ * @return The current Builder.
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ public Builder setMaxCaptureStages(int maxCaptureStages) {
+ getMutableConfig().insertOption(OPTION_MAX_CAPTURE_STAGES, maxCaptureStages);
+ return this;
+ }
+
// Implementations of TargetConfig.Builder default methods
/** @hide */
@@ -772,5 +830,14 @@
getMutableConfig().insertOption(OPTION_SURFACE_OCCUPANCY_PRIORITY, priority);
return this;
}
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Override
+ public Builder setUseCaseEventListener(UseCase.EventListener useCaseEventListener) {
+ getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_LISTENER, useCaseEventListener);
+ return this;
+ }
+
}
}
diff --git a/camera/core/src/main/java/androidx/camera/core/MultiValueSet.java b/camera/core/src/main/java/androidx/camera/core/MultiValueSet.java
new file mode 100644
index 0000000..205a2cb
--- /dev/null
+++ b/camera/core/src/main/java/androidx/camera/core/MultiValueSet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019 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 androidx.camera.core;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A value set implementation that store multiple values in type C.
+ *
+ * @param <C> The type of the parameter.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class MultiValueSet<C> {
+
+ private Set<C> mSet = new HashSet<>();
+
+ /**
+ * Adds all of the elements in the specified collection to this value set if they're not already
+ * present (optional operation).
+ *
+ * @param value collection containing elements to be added to this value.
+ */
+ public void addAll(List<C> value) {
+ mSet.addAll(value);
+ }
+
+ /**
+ * Returns the list of {@link C} which containing all the elements were added to this value set.
+ */
+ @NonNull
+ public List<C> getAllItems() {
+ return Collections.unmodifiableList(new ArrayList<>(mSet));
+ }
+
+ /**
+ * Need to implement the clone method for object copy.
+ * @return the cloned instance.
+ */
+ public abstract MultiValueSet<C> clone();
+}
diff --git a/camera/core/src/main/java/androidx/camera/core/PreviewConfig.java b/camera/core/src/main/java/androidx/camera/core/PreviewConfig.java
index 8514de2..7762f378 100644
--- a/camera/core/src/main/java/androidx/camera/core/PreviewConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/PreviewConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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.
@@ -343,15 +343,32 @@
return retrieveOption(OPTION_SURFACE_OCCUPANCY_PRIORITY);
}
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener(
+ @Nullable UseCase.EventListener valueIfMissing) {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER, valueIfMissing);
+ }
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener() {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER);
+ }
+
// End of the default implementation of Config
// *********************************************************************************************
/** Builder for a {@link PreviewConfig}. */
public static final class Builder
- implements UseCaseConfig.Builder<Preview, PreviewConfig, PreviewConfig.Builder>,
- ImageOutputConfig.Builder<PreviewConfig.Builder>,
- CameraDeviceConfig.Builder<PreviewConfig.Builder>,
- ThreadConfig.Builder<PreviewConfig.Builder> {
+ implements UseCaseConfig.Builder<Preview, PreviewConfig, Builder>,
+ ImageOutputConfig.Builder<Builder>,
+ CameraDeviceConfig.Builder<Builder>,
+ ThreadConfig.Builder<Builder> {
private final MutableOptionsBundle mMutableConfig;
@@ -559,5 +576,13 @@
return this;
}
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Override
+ public Builder setUseCaseEventListener(UseCase.EventListener useCaseEventListener) {
+ getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_LISTENER, useCaseEventListener);
+ return this;
+ }
+
}
}
diff --git a/camera/core/src/main/java/androidx/camera/core/SessionConfig.java b/camera/core/src/main/java/androidx/camera/core/SessionConfig.java
index 638e049..1bdd9c1 100644
--- a/camera/core/src/main/java/androidx/camera/core/SessionConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/SessionConfig.java
@@ -344,6 +344,11 @@
mCaptureConfigBuilder.setImplementationOptions(config);
}
+ /** Add a set of {@link Config} to the implementation specific options. */
+ public void addImplementationOptions(Config config) {
+ mCaptureConfigBuilder.addImplementationOptions(config);
+ }
+
/**
* Builds an instance of a SessionConfig that has all the combined parameters of the
* SessionConfig that have been added to the Builder.
diff --git a/camera/core/src/main/java/androidx/camera/core/UseCase.java b/camera/core/src/main/java/androidx/camera/core/UseCase.java
index 3962b40..3259e01 100644
--- a/camera/core/src/main/java/androidx/camera/core/UseCase.java
+++ b/camera/core/src/main/java/androidx/camera/core/UseCase.java
@@ -330,6 +330,11 @@
/** Clears internal state of this use case. */
@CallSuper
protected void clear() {
+ EventListener eventListener = mUseCaseConfig.getUseCaseEventListener(null);
+ if (eventListener != null) {
+ eventListener.onUnbind();
+ }
+
mListeners.clear();
}
@@ -407,6 +412,20 @@
}
/**
+ * Called when use case is binding to life cycle via
+ * {@link CameraX#bindToLifecycle(LifecycleOwner, UseCase...)}.
+ *
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ protected void onBind() {
+ EventListener eventListener = mUseCaseConfig.getUseCaseEventListener(null);
+ if (eventListener != null) {
+ eventListener.onBind(getCameraIdUnchecked());
+ }
+ }
+
+ /**
* Retrieves a previously attached {@link CameraControl}.
*
* @hide
@@ -484,4 +503,39 @@
*/
void onUseCaseReset(UseCase useCase);
}
+
+ /**
+ * Listener called when a {@link UseCase} transitions between bind/unbind states.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public interface EventListener {
+
+ /**
+ * Called when use case was bound to the life cycle.
+ * @param cameraId that current used.
+ */
+ void onBind(String cameraId);
+
+ /**
+ * Called when use case was unbind from the life cycle and clear the resource of the use
+ * case.
+ */
+ void onUnbind();
+ }
+
+ private String getCameraIdUnchecked() {
+ String cameraId = null;
+ LensFacing lensFacing = mUseCaseConfig.retrieveOption(
+ CameraDeviceConfig.OPTION_LENS_FACING);
+ try {
+ cameraId = CameraX.getCameraWithLensFacing(lensFacing);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid camera lens facing: " + lensFacing, e);
+ }
+
+ return cameraId;
+ }
+
}
diff --git a/camera/core/src/main/java/androidx/camera/core/UseCaseConfig.java b/camera/core/src/main/java/androidx/camera/core/UseCaseConfig.java
index 3f97c86c..11c2b4c 100644
--- a/camera/core/src/main/java/androidx/camera/core/UseCaseConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/UseCaseConfig.java
@@ -28,7 +28,8 @@
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
-public interface UseCaseConfig<T extends UseCase> extends TargetConfig<T>, Config {
+public interface UseCaseConfig<T extends UseCase> extends TargetConfig<T>, Config,
+ UseCaseEventConfig {
// Option Declarations:
// *********************************************************************************************
@@ -156,7 +157,7 @@
*/
@RestrictTo(Scope.LIBRARY_GROUP)
interface Builder<T extends UseCase, C extends UseCaseConfig<T>, B> extends
- TargetConfig.Builder<T, B>, ExtendableBuilder {
+ TargetConfig.Builder<T, B>, ExtendableBuilder, UseCaseEventConfig.Builder<B> {
/**
* Sets the default session configuration for this use case.
diff --git a/camera/core/src/main/java/androidx/camera/core/UseCaseEventConfig.java b/camera/core/src/main/java/androidx/camera/core/UseCaseEventConfig.java
new file mode 100644
index 0000000..9475a9b
--- /dev/null
+++ b/camera/core/src/main/java/androidx/camera/core/UseCaseEventConfig.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 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 androidx.camera.core;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+/** Configuration containing options pertaining to EventListener object. */
+public interface UseCaseEventConfig {
+
+ /**
+ * Option: camerax.core.useCaseEventListener
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ Config.Option<UseCase.EventListener> OPTION_USE_CASE_EVENT_LISTENER =
+ Config.Option.create("camerax.core.useCaseEventListener", UseCase.EventListener.class);
+
+ /**
+ * Returns the EventListener.
+ *
+ * @param valueIfMissing The value to return if this configuration option has not been set.
+ * @return The stored value or <code>valueIfMissing</code> if the value does not exist in this
+ * configuration.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @Nullable
+ UseCase.EventListener getUseCaseEventListener(@Nullable UseCase.EventListener valueIfMissing);
+
+ /**
+ * Returns the EventListener.
+ *
+ * @return The stored value, if it exists in this configuration.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @Nullable
+ UseCase.EventListener getUseCaseEventListener();
+
+ // Option Declarations:
+ // *********************************************************************************************
+
+ /**
+ * Builder for a {@link UseCaseEventConfig}.
+ *
+ * @param <B> The top level builder type for which this builder is composed with.
+ */
+ interface Builder<B> {
+
+ /**
+ * Sets the EventListener.
+ *
+ * @param eventListener The EventListener.
+ * @return the current Builder.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ B setUseCaseEventListener(UseCase.EventListener eventListener);
+ }
+}
diff --git a/camera/core/src/main/java/androidx/camera/core/VideoCaptureConfig.java b/camera/core/src/main/java/androidx/camera/core/VideoCaptureConfig.java
index cf99ba8..bd01d0f 100644
--- a/camera/core/src/main/java/androidx/camera/core/VideoCaptureConfig.java
+++ b/camera/core/src/main/java/androidx/camera/core/VideoCaptureConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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.
@@ -547,16 +547,33 @@
return retrieveOption(OPTION_SURFACE_OCCUPANCY_PRIORITY);
}
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener(
+ @Nullable UseCase.EventListener valueIfMissing) {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER, valueIfMissing);
+ }
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener() {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER);
+ }
+
// End of the default implementation of Config
// *********************************************************************************************
/** Builder for a {@link VideoCaptureConfig}. */
public static final class Builder
implements
- UseCaseConfig.Builder<VideoCapture, VideoCaptureConfig, VideoCaptureConfig.Builder>,
- ImageOutputConfig.Builder<VideoCaptureConfig.Builder>,
- CameraDeviceConfig.Builder<VideoCaptureConfig.Builder>,
- ThreadConfig.Builder<VideoCaptureConfig.Builder> {
+ UseCaseConfig.Builder<VideoCapture, VideoCaptureConfig, Builder>,
+ ImageOutputConfig.Builder<Builder>,
+ CameraDeviceConfig.Builder<Builder>,
+ ThreadConfig.Builder<Builder> {
private final MutableOptionsBundle mMutableConfig;
@@ -849,5 +866,13 @@
getMutableConfig().insertOption(OPTION_SURFACE_OCCUPANCY_PRIORITY, priority);
return this;
}
+
+ /** @hide */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @Override
+ public Builder setUseCaseEventListener(UseCase.EventListener useCaseEventListener) {
+ getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_LISTENER, useCaseEventListener);
+ return this;
+ }
}
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index 185e83c..d30fc28 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -15,6 +15,7 @@
*/
package androidx.camera.extensions.impl;
+import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
@@ -23,6 +24,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Size;
import android.view.Surface;
import java.nio.ByteBuffer;
@@ -39,6 +41,7 @@
public final class BokehImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
private static final String TAG = "BokehICExtender";
private static final int DEFAULT_STAGE_ID = 0;
+ private static final int SESSION_STAGE_ID = 101;
public BokehImageCaptureExtenderImpl() {
}
@@ -109,7 +112,67 @@
Log.d(TAG, "Completed bokeh CaptureProcessor");
}
+
+ @Override
+ public void onResolutionUpdate(Size size) {
+
+ }
+
+ @Override
+ public void onImageFormatUpdate(int imageFormat) {
+
+ }
};
return captureProcessor;
}
+
+ @Override
+ public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+ Context context) {
+
+ }
+
+ @Override
+ public void onDeInit() {
+
+ }
+
+ @Override
+ public CaptureStageImpl onPresetSession() {
+ // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
+ // placeholder set of CaptureRequest.Key values
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
+ CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+
+ return captureStage;
+ }
+
+ @Override
+ public CaptureStageImpl onEnableSession() {
+ // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
+ // placeholder set of CaptureRequest.Key values
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
+ CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+
+ return captureStage;
+ }
+
+ @Override
+ public CaptureStageImpl onDisableSession() {
+ // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
+ // placeholder set of CaptureRequest.Key values
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
+ CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+
+ return captureStage;
+ }
+
+ @Override
+ public int getMaxCaptureStage() {
+ return 3;
+ }
+
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
index aa3516a..694b511 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -15,6 +15,7 @@
*/
package androidx.camera.extensions.impl;
+import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
@@ -26,6 +27,7 @@
*/
public final class BokehPreviewExtenderImpl implements PreviewExtenderImpl {
private static final int DEFAULT_STAGE_ID = 0;
+ private static final int SESSION_STAGE_ID = 101;
public BokehPreviewExtenderImpl() {}
@@ -60,4 +62,48 @@
public RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor() {
return RequestUpdateProcessorImpls.noUpdateProcessor();
}
+
+ @Override
+ public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+ Context context) {
+
+ }
+
+ @Override
+ public void onDeInit() {
+
+ }
+
+ @Override
+ public CaptureStageImpl onPresetSession() {
+ // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
+ // placeholder set of CaptureRequest.Key values
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
+ CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+
+ return captureStage;
+ }
+
+ @Override
+ public CaptureStageImpl onEnableSession() {
+ // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
+ // placeholder set of CaptureRequest.Key values
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
+ CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+
+ return captureStage;
+ }
+
+ @Override
+ public CaptureStageImpl onDisableSession() {
+ // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
+ // placeholder set of CaptureRequest.Key values
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
+ CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+
+ return captureStage;
+ }
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
index 04c7247..0b8bdd5 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -19,6 +19,7 @@
import android.hardware.camera2.TotalCaptureResult;
import android.media.Image;
import android.util.Pair;
+import android.util.Size;
import android.view.Surface;
import java.util.Map;
@@ -46,4 +47,22 @@
* so no references to them should be kept.
*/
void process(Map<Integer, Pair<Image, TotalCaptureResult>> results);
+
+ /**
+ * This callback will be invoked when CameraX changes the configured input resolution. After
+ * this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as input
+ * to be at the specified resolution.
+ *
+ * @param size for the surface.
+ */
+ void onResolutionUpdate(Size size);
+
+ /**
+ * This callback will be invoked when CameraX changes the configured input image format.
+ * After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+ * input to have the specified image format.
+ *
+ * @param imageFormat for the surface.
+ */
+ void onImageFormatUpdate(int imageFormat);
}
\ No newline at end of file
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
new file mode 100644
index 0000000..5bacab7
--- /dev/null
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 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 androidx.camera.extensions.impl;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+
+/**
+ * Provides interfaces that the OEM needs to implement to handle the state change.
+ */
+public interface ExtenderStateListener {
+
+ /**
+ * Notify to initialize the extension. This will be called after bindToLifeCycle. This is
+ * where the use case is started and would be able to allocate resources here. After onInit() is
+ * called, the camera ID, cameraCharacteristics and context will not change until onDeInit()
+ * has been called.
+ *
+ * @param cameraId The camera2 id string of the camera.
+ * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+ * @param context The {@link Context} used for CameraX.
+ */
+ void onInit(String cameraId, CameraCharacteristics cameraCharacteristics, Context context);
+
+ /**
+ * Notify to de-initialize the extension. This callback will be invoked after unbind.
+ * After onDeInit() was called, it is expected that the camera ID, cameraCharacteristics will
+ * no longer hold, this should be where to clear all resources allocated for this use case.
+ */
+ void onDeInit();
+
+ /**
+ * This will be invoked before creating a
+ * {@link android.hardware.camera2.CameraCaptureSession}. The {@link CaptureRequest}
+ * parameters returned via {@link CaptureStageImpl} will be passed to the camera device as
+ * part of the capture session initialization via setSessionParameters(). The valid parameter
+ * is a subset of the available capture request parameters.
+ *
+ * @return The request information to set the session wide camera parameters.
+ */
+ CaptureStageImpl onPresetSession();
+
+ /**
+ * This will be invoked once after the {@link android.hardware.camera2.CameraCaptureSession}
+ * has been created. The {@link CaptureRequest} parameters returned via
+ * {@link CaptureStageImpl} will be used to generate a single request to the current
+ * configured {@link CameraDevice}. The generated request will be submitted to camera before
+ * processing other single requests.
+ *
+ * @return The request information to create a single capture request to camera device.
+ */
+ CaptureStageImpl onEnableSession();
+
+ /**
+ * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+ * closed. The {@link CaptureRequest} parameters returned via {@link CaptureStageImpl} will
+ * be used to generate a single request to the currently configured {@link CameraDevice}. The
+ * generated request will be submitted to camera before the CameraCaptureSession is closed.
+ *
+ * @return The request information to customize the session.
+ */
+ CaptureStageImpl onDisableSession();
+}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index 96df4c2..ca7b143 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -15,6 +15,7 @@
*/
package androidx.camera.extensions.impl;
+import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
@@ -23,6 +24,7 @@
import android.os.Build;
import android.util.Log;
import android.util.Pair;
+import android.util.Size;
import android.view.Surface;
import java.nio.ByteBuffer;
@@ -42,6 +44,7 @@
private static final int UNDER_STAGE_ID = 0;
private static final int NORMAL_STAGE_ID = 1;
private static final int OVER_STAGE_ID = 2;
+ private static final int SESSION_STAGE_ID = 101;
public HdrImageCaptureExtenderImpl() {
}
@@ -146,7 +149,52 @@
Log.d(TAG, "Completed HDR CaptureProcessor");
}
+
+ @Override
+ public void onResolutionUpdate(Size size) {
+
+ }
+
+ @Override
+ public void onImageFormatUpdate(int imageFormat) {
+
+ }
};
return captureProcessor;
}
+
+ @Override
+ public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+ Context context) {
+
+ }
+
+ @Override
+ public void onDeInit() {
+
+ }
+
+ @Override
+ public CaptureStageImpl onPresetSession() {
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ return captureStage;
+ }
+
+ @Override
+ public CaptureStageImpl onEnableSession() {
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ return captureStage;
+ }
+
+ @Override
+ public CaptureStageImpl onDisableSession() {
+ SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
+ return captureStage;
+ }
+
+ @Override
+ public int getMaxCaptureStage() {
+ return 4;
+ }
+
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index 4f3cf65..f33b561 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -16,6 +16,7 @@
package androidx.camera.extensions.impl;
+import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
@@ -47,7 +48,7 @@
// placeholder set of CaptureRequest.Key values
SettableCaptureStage captureStage = new SettableCaptureStage(DEFAULT_STAGE_ID);
captureStage.addCaptureRequestParameters(CaptureRequest.CONTROL_EFFECT_MODE,
- CaptureRequest.CONTROL_EFFECT_MODE_SEPIA);
+ CaptureRequest.CONTROL_EFFECT_MODE_AQUA);
return captureStage;
}
@@ -61,4 +62,30 @@
public RequestUpdateProcessorImpl getRequestUpdatePreviewProcessor() {
return RequestUpdateProcessorImpls.noUpdateProcessor();
}
+
+ @Override
+ public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+ Context context) {
+
+ }
+
+ @Override
+ public void onDeInit() {
+
+ }
+
+ @Override
+ public CaptureStageImpl onPresetSession() {
+ return null;
+ }
+
+ @Override
+ public CaptureStageImpl onEnableSession() {
+ return null;
+ }
+
+ @Override
+ public CaptureStageImpl onDisableSession() {
+ return null;
+ }
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index 94d40d5..a54ead2 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -23,7 +23,7 @@
/**
* Provides abstract methods that the OEM needs to implement to enable extensions for image capture.
*/
-public interface ImageCaptureExtenderImpl {
+public interface ImageCaptureExtenderImpl extends ExtenderStateListener {
/**
* Indicates whether the extension is supported on the device.
*
@@ -41,12 +41,18 @@
*/
void enableExtension(String cameraId, CameraCharacteristics cameraCharacteristics);
- /** The set of captures that are needed to create an image with the effect. */
- List<CaptureStageImpl> getCaptureStages();
-
/**
* The processing that will be done on a set of captures to create and image with the effect.
*/
CaptureProcessorImpl getCaptureProcessor();
+
+ /** The set of captures that are needed to create an image with the effect. */
+ List<CaptureStageImpl> getCaptureStages();
+
+ /**
+ * Returns the maximum size of the list returned by {@link #getCaptureStages()}.
+ * @return the maximum count.
+ */
+ int getMaxCaptureStage();
}
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
index 9402782..79ff4db 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -21,7 +21,7 @@
/**
* Provides abstract methods that the OEM needs to implement to enable extensions in the preview.
*/
-public interface PreviewExtenderImpl {
+public interface PreviewExtenderImpl extends ExtenderStateListener {
/** The different types of the preview processing. */
enum ProcessorType {
/** Processing which only updates the {@link CaptureStageImpl}. */
diff --git a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfig.java b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfig.java
index f51bb3a..35aaab7 100644
--- a/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfig.java
+++ b/camera/testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfig.java
@@ -24,6 +24,7 @@
import androidx.camera.core.MutableOptionsBundle;
import androidx.camera.core.OptionsBundle;
import androidx.camera.core.SessionConfig;
+import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseConfig;
import java.util.Set;
@@ -152,6 +153,19 @@
return retrieveOption(OPTION_SURFACE_OCCUPANCY_PRIORITY);
}
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener(
+ @Nullable UseCase.EventListener valueIfMissing) {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER, valueIfMissing);
+ }
+
+ @Nullable
+ @Override
+ public UseCase.EventListener getUseCaseEventListener() {
+ return retrieveOption(OPTION_USE_CASE_EVENT_LISTENER);
+ }
+
// End of the default implementation of Config
// *********************************************************************************************
@@ -227,5 +241,11 @@
getMutableConfig().insertOption(OPTION_SURFACE_OCCUPANCY_PRIORITY, priority);
return this;
}
+
+ @Override
+ public Builder setUseCaseEventListener(UseCase.EventListener eventListener) {
+ getMutableConfig().insertOption(OPTION_USE_CASE_EVENT_LISTENER, eventListener);
+ return this;
+ }
}
}
diff --git a/collection/api/1.2.0-alpha01.txt b/collection/api/1.2.0-alpha01.txt
new file mode 100644
index 0000000..179d17f
--- /dev/null
+++ b/collection/api/1.2.0-alpha01.txt
@@ -0,0 +1,183 @@
+// Signature format: 3.0
+package androidx.collection {
+
+ public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+ ctor public ArrayMap();
+ ctor public ArrayMap(int);
+ ctor public ArrayMap(androidx.collection.SimpleArrayMap!);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public java.util.Set<java.util.Map.Entry<K,V>>! entrySet();
+ method public java.util.Set<K>! keySet();
+ method public void putAll(java.util.Map<? extends K,? extends V>!);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public java.util.Collection<V>! values();
+ }
+
+ public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
+ ctor public ArraySet();
+ ctor public ArraySet(int);
+ ctor public ArraySet(androidx.collection.ArraySet<E>?);
+ ctor public ArraySet(java.util.Collection<E>?);
+ method public boolean add(E?);
+ method public void addAll(androidx.collection.ArraySet<? extends E>);
+ method public boolean addAll(java.util.Collection<? extends E>);
+ method public void clear();
+ method public boolean contains(Object?);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public void ensureCapacity(int);
+ method public int indexOf(Object?);
+ method public boolean isEmpty();
+ method public java.util.Iterator<E>! iterator();
+ method public boolean remove(Object?);
+ method public boolean removeAll(androidx.collection.ArraySet<? extends E>);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public E! removeAt(int);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public int size();
+ method public Object[] toArray();
+ method public <T> T[] toArray(T[]);
+ method public E? valueAt(int);
+ }
+
+ public final class CircularArray<E> {
+ ctor public CircularArray();
+ ctor public CircularArray(int);
+ method public void addFirst(E!);
+ method public void addLast(E!);
+ method public void clear();
+ method public E! get(int);
+ method public E! getFirst();
+ method public E! getLast();
+ method public boolean isEmpty();
+ method public E! popFirst();
+ method public E! popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public final class CircularIntArray {
+ ctor public CircularIntArray();
+ ctor public CircularIntArray(int);
+ method public void addFirst(int);
+ method public void addLast(int);
+ method public void clear();
+ method public int get(int);
+ method public int getFirst();
+ method public int getLast();
+ method public boolean isEmpty();
+ method public int popFirst();
+ method public int popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public class LongSparseArray<E> implements java.lang.Cloneable {
+ ctor public LongSparseArray();
+ ctor public LongSparseArray(int);
+ method public void append(long, E!);
+ method public void clear();
+ method public androidx.collection.LongSparseArray<E>! clone();
+ method public boolean containsKey(long);
+ method public boolean containsValue(E!);
+ method @Deprecated public void delete(long);
+ method public E? get(long);
+ method public E! get(long, E!);
+ method public int indexOfKey(long);
+ method public int indexOfValue(E!);
+ method public boolean isEmpty();
+ method public long keyAt(int);
+ method public void put(long, E!);
+ method public void putAll(androidx.collection.LongSparseArray<? extends E>);
+ method public E? putIfAbsent(long, E!);
+ method public void remove(long);
+ method public boolean remove(long, Object!);
+ method public void removeAt(int);
+ method public E? replace(long, E!);
+ method public boolean replace(long, E!, E!);
+ method public void setValueAt(int, E!);
+ method public int size();
+ method public E! valueAt(int);
+ }
+
+ public class LruCache<K, V> {
+ ctor public LruCache(int);
+ method protected V? create(K);
+ method public final int createCount();
+ method protected void entryRemoved(boolean, K, V, V?);
+ method public final void evictAll();
+ method public final int evictionCount();
+ method public final V? get(K);
+ method public final int hitCount();
+ method public final int maxSize();
+ method public final int missCount();
+ method public final V? put(K, V);
+ method public final int putCount();
+ method public final V? remove(K);
+ method public void resize(int);
+ method public final int size();
+ method protected int sizeOf(K, V);
+ method public final java.util.Map<K,V>! snapshot();
+ method public final String toString();
+ method public void trimToSize(int);
+ }
+
+ public class SimpleArrayMap<K, V> {
+ ctor public SimpleArrayMap();
+ ctor public SimpleArrayMap(int);
+ ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<K,V>!);
+ method public void clear();
+ method public boolean containsKey(Object?);
+ method public boolean containsValue(Object!);
+ method public void ensureCapacity(int);
+ method public V? get(Object!);
+ method public V! getOrDefault(Object!, V!);
+ method public int indexOfKey(Object?);
+ method public boolean isEmpty();
+ method public K! keyAt(int);
+ method public V? put(K!, V!);
+ method public void putAll(androidx.collection.SimpleArrayMap<? extends K,? extends V>);
+ method public V? putIfAbsent(K!, V!);
+ method public V? remove(Object!);
+ method public boolean remove(Object!, Object!);
+ method public V! removeAt(int);
+ method public V? replace(K!, V!);
+ method public boolean replace(K!, V!, V!);
+ method public V! setValueAt(int, V!);
+ method public int size();
+ method public V! valueAt(int);
+ }
+
+ public class SparseArrayCompat<E> implements java.lang.Cloneable {
+ ctor public SparseArrayCompat();
+ ctor public SparseArrayCompat(int);
+ method public void append(int, E!);
+ method public void clear();
+ method public androidx.collection.SparseArrayCompat<E>! clone();
+ method public boolean containsKey(int);
+ method public boolean containsValue(E!);
+ method @Deprecated public void delete(int);
+ method public E? get(int);
+ method public E! get(int, E!);
+ method public int indexOfKey(int);
+ method public int indexOfValue(E!);
+ method public boolean isEmpty();
+ method public int keyAt(int);
+ method public void put(int, E!);
+ method public void putAll(androidx.collection.SparseArrayCompat<? extends E>);
+ method public E? putIfAbsent(int, E!);
+ method public void remove(int);
+ method public boolean remove(int, Object!);
+ method public void removeAt(int);
+ method public void removeAtRange(int, int);
+ method public E? replace(int, E!);
+ method public boolean replace(int, E!, E!);
+ method public void setValueAt(int, E!);
+ method public int size();
+ method public E! valueAt(int);
+ }
+
+}
+
diff --git a/collection/api/restricted_1.2.0-alpha01.txt b/collection/api/restricted_1.2.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/collection/api/restricted_1.2.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/collection/ktx/api/1.2.0-alpha01.txt b/collection/ktx/api/1.2.0-alpha01.txt
new file mode 100644
index 0000000..3fe6a36
--- /dev/null
+++ b/collection/ktx/api/1.2.0-alpha01.txt
@@ -0,0 +1,52 @@
+// Signature format: 3.0
+package androidx.collection {
+
+ public final class ArrayMapKt {
+ ctor public ArrayMapKt();
+ method public static inline <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf();
+ method public static <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf(kotlin.Pair<? extends K,? extends V>... pairs);
+ }
+
+ public final class ArraySetKt {
+ ctor public ArraySetKt();
+ method public static inline <T> androidx.collection.ArraySet<T> arraySetOf();
+ method public static <T> androidx.collection.ArraySet<T> arraySetOf(T... values);
+ }
+
+ public final class LongSparseArrayKt {
+ ctor public LongSparseArrayKt();
+ method public static inline operator <T> boolean contains(androidx.collection.LongSparseArray<T>, long key);
+ method public static inline <T> void forEach(androidx.collection.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(androidx.collection.LongSparseArray<T>, long key, T! defaultValue);
+ method public static inline <T> T! getOrElse(androidx.collection.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(androidx.collection.LongSparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(androidx.collection.LongSparseArray<T>);
+ method public static <T> kotlin.collections.LongIterator keyIterator(androidx.collection.LongSparseArray<T>);
+ method public static operator <T> androidx.collection.LongSparseArray<T> plus(androidx.collection.LongSparseArray<T>, androidx.collection.LongSparseArray<T> other);
+ method @Deprecated public static <T> boolean remove(androidx.collection.LongSparseArray<T>, long key, T! value);
+ method public static inline operator <T> void set(androidx.collection.LongSparseArray<T>, long key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.LongSparseArray<T>);
+ }
+
+ public final class LruCacheKt {
+ ctor public LruCacheKt();
+ method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> _, _, _, _ -> });
+ }
+
+ public final class SparseArrayKt {
+ ctor public SparseArrayKt();
+ method public static inline operator <T> boolean contains(androidx.collection.SparseArrayCompat<T>, int key);
+ method public static inline <T> void forEach(androidx.collection.SparseArrayCompat<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(androidx.collection.SparseArrayCompat<T>, int key, T! defaultValue);
+ method public static inline <T> T! getOrElse(androidx.collection.SparseArrayCompat<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(androidx.collection.SparseArrayCompat<T>);
+ method public static inline <T> boolean isNotEmpty(androidx.collection.SparseArrayCompat<T>);
+ method public static <T> kotlin.collections.IntIterator keyIterator(androidx.collection.SparseArrayCompat<T>);
+ method public static operator <T> androidx.collection.SparseArrayCompat<T> plus(androidx.collection.SparseArrayCompat<T>, androidx.collection.SparseArrayCompat<T> other);
+ method @Deprecated public static <T> boolean remove(androidx.collection.SparseArrayCompat<T>, int key, T! value);
+ method public static inline operator <T> void set(androidx.collection.SparseArrayCompat<T>, int key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.SparseArrayCompat<T>);
+ }
+
+}
+
diff --git a/collection/ktx/api/restricted_1.2.0-alpha01.txt b/collection/ktx/api/restricted_1.2.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/collection/ktx/api/restricted_1.2.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/collection/ktx/build.gradle b/collection/ktx/build.gradle
index e813c94..3b51b3d 100644
--- a/collection/ktx/build.gradle
+++ b/collection/ktx/build.gradle
@@ -24,7 +24,7 @@
}
dependencies {
- compile(project(":collection"))
+ compile("androidx.collection:collection:1.1.0-rc01")
compile(KOTLIN_STDLIB)
testCompile(JUNIT)
testCompile(TRUTH)
diff --git a/compose/README.md b/compose/README.md
index 0cc9154..be48786 100644
--- a/compose/README.md
+++ b/compose/README.md
@@ -2,7 +2,7 @@
Jetpack Compose makes it easy to write and manage an application's frontend by providing a declarative API that allows users to update their Android application UI without imperatively mutating frontend views.
-A Compose application is comprised of `@Composable` functions, which are [pure functions](https://en.wikipedia.org/wiki/Pure_function) that transform application data into a UI hierarchy. When the underlying data changes, the Composable functions can be re-invoked to generate an updated UI hierarchy.
+A Compose application is comprised of `@Composable` functions, which are functions that transform application data into a UI hierarchy. When the underlying data changes, the Composable functions can be re-invoked to generate an updated UI hierarchy.
```
import androidx.compose.*
diff --git a/content/build.gradle b/content/build.gradle
index 1be69c9..91fa5e9 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0-rc01")
api(project(":core"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(JUNIT)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/customview/build.gradle b/customview/build.gradle
index 1a08bcd..738f886 100644
--- a/customview/build.gradle
+++ b/customview/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0-rc01")
api(project(":core"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(JUNIT)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index 03ceb41..e9d5ab2 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api(project(":core"))
- api(project(":collection"))
+ api("androidx.collection:collection:1.1.0-rc01")
api(project(":legacy-support-core-utils"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/enterprise/feedback/api/1.0.0-alpha02.txt b/enterprise/feedback/api/1.0.0-alpha02.txt
index e85daff..7aa61dc 100644
--- a/enterprise/feedback/api/1.0.0-alpha02.txt
+++ b/enterprise/feedback/api/1.0.0-alpha02.txt
@@ -22,12 +22,9 @@
method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setSeverity(int);
}
- public class KeyedAppStatesReporter {
- method public static androidx.enterprise.feedback.KeyedAppStatesReporter getInstance(android.content.Context);
- method public static void initialize(android.content.Context, java.util.concurrent.Executor);
- method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
- method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
- field public static final String ACTION_APP_STATES = "androidx.enterprise.feedback.action.APP_STATES";
+ public abstract class KeyedAppStatesReporter {
+ method public abstract void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
+ method public abstract void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
}
public abstract class KeyedAppStatesService extends android.app.Service {
@@ -56,5 +53,12 @@
method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setTimestamp(long);
}
+ public class SingletonKeyedAppStatesReporter extends androidx.enterprise.feedback.KeyedAppStatesReporter {
+ method public static androidx.enterprise.feedback.KeyedAppStatesReporter getInstance(android.content.Context);
+ method public static void initialize(android.content.Context, java.util.concurrent.Executor);
+ method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
+ method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
+ }
+
}
diff --git a/enterprise/feedback/api/current.txt b/enterprise/feedback/api/current.txt
index e85daff..7aa61dc 100644
--- a/enterprise/feedback/api/current.txt
+++ b/enterprise/feedback/api/current.txt
@@ -22,12 +22,9 @@
method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setSeverity(int);
}
- public class KeyedAppStatesReporter {
- method public static androidx.enterprise.feedback.KeyedAppStatesReporter getInstance(android.content.Context);
- method public static void initialize(android.content.Context, java.util.concurrent.Executor);
- method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
- method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
- field public static final String ACTION_APP_STATES = "androidx.enterprise.feedback.action.APP_STATES";
+ public abstract class KeyedAppStatesReporter {
+ method public abstract void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
+ method public abstract void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
}
public abstract class KeyedAppStatesService extends android.app.Service {
@@ -56,5 +53,12 @@
method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setTimestamp(long);
}
+ public class SingletonKeyedAppStatesReporter extends androidx.enterprise.feedback.KeyedAppStatesReporter {
+ method public static androidx.enterprise.feedback.KeyedAppStatesReporter getInstance(android.content.Context);
+ method public static void initialize(android.content.Context, java.util.concurrent.Executor);
+ method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
+ method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState>);
+ }
+
}
diff --git a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java
index 0fea1b5..3dc36fc 100644
--- a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java
+++ b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java
@@ -16,48 +16,27 @@
package androidx.enterprise.feedback;
-import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Build;
-import android.os.Bundle;
import android.os.Message;
import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
/**
* A reporter of keyed app states to enable communication between an app and an EMM (enterprise
* mobility management).
+ *
+ * For production use {@link SingletonKeyedAppStatesReporter}.
*/
-public class KeyedAppStatesReporter {
+public abstract class KeyedAppStatesReporter {
- private static final String LOG_TAG = "KeyedAppStatesReporter";
+ // Package-private constructor to restrict subclasses to the same package
+ KeyedAppStatesReporter() {}
static final String PHONESKY_PACKAGE_NAME = "com.android.vending";
- @SuppressLint("StaticFieldLeak") // Application Context only.
- private static volatile KeyedAppStatesReporter sSingleton;
-
/** The value of {@link Message#what} to indicate a state update. */
static final int WHAT_STATE = 1;
@@ -99,85 +78,15 @@
static final String APP_STATE_DATA = "androidx.enterprise.feedback.APP_STATE_DATA";
/** The intent action for reporting app states. */
- public static final String ACTION_APP_STATES = "androidx.enterprise.feedback.action.APP_STATES";
+ static final String ACTION_APP_STATES = "androidx.enterprise.feedback.action.APP_STATES";
- private final Context mContext;
+ static boolean canPackageReceiveAppStates(Context context, String packageName) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
- private final Map<String, BufferedServiceConnection> mServiceConnections = new HashMap<>();
-
- private static final int EXECUTOR_IDLE_ALIVE_TIME_SECS = 20;
- private final Executor mExecutor;
-
- /**
- * Creates an {@link ExecutorService} which has no persistent background thread, and ensures
- * tasks will run in submit order.
- */
- private static ExecutorService createExecutorService() {
- return new ThreadPoolExecutor(
- /* corePoolSize= */ 0,
- /* maximumPoolSize= */ 1,
- EXECUTOR_IDLE_ALIVE_TIME_SECS,
- TimeUnit.SECONDS,
- new LinkedBlockingQueue<Runnable>() /* Not used */);
- }
-
- /**
- * Sets executor used to construct the singleton.
- *
- * <p>If required, this method must be called before calling {@link #getInstance(Context)}.
- *
- * <p>If this method is not called, the reporter will run on a newly-created thread.
- * This newly-created thread will be cleaned up and recreated as necessary when idle.
- */
- public static void initialize(@NonNull Context context, @NonNull Executor executor) {
- if (context == null || executor == null) {
- throw new NullPointerException();
- }
- synchronized (KeyedAppStatesReporter.class) {
- if (sSingleton != null) {
- throw new IllegalStateException(
- "initialize can only be called once and must be called before "
- + "calling getInstance.");
- }
- initializeSingleton(context, executor);
- }
- }
-
- /**
- * Returns an instance of the reporter.
- *
- * <p>Creates and initializes an instance if one doesn't already exist.
- */
- @NonNull
- public static KeyedAppStatesReporter getInstance(@NonNull Context context) {
- if (context == null || context.getApplicationContext() == null) {
- throw new NullPointerException();
- }
- if (sSingleton == null) {
- synchronized (KeyedAppStatesReporter.class) {
- if (sSingleton == null) {
- initializeSingleton(context, createExecutorService());
- }
- }
- }
- return sSingleton;
- }
-
- private static void initializeSingleton(@NonNull Context context, @NonNull Executor executor) {
- sSingleton = new KeyedAppStatesReporter(context, executor);
- sSingleton.bind();
- }
-
- @VisibleForTesting
- static void resetSingleton() {
- synchronized (KeyedAppStatesReporter.class) {
- sSingleton = null;
- }
- }
-
- private KeyedAppStatesReporter(Context context, Executor executor) {
- this.mContext = context.getApplicationContext();
- this.mExecutor = executor;
+ return packageName.equals(PHONESKY_PACKAGE_NAME)
+ || devicePolicyManager.isDeviceOwnerApp(packageName)
+ || devicePolicyManager.isProfileOwnerApp(packageName);
}
/**
@@ -201,25 +110,7 @@
*
* @see #setStatesImmediate(Collection)
*/
- public void setStates(@NonNull Collection<KeyedAppState> states) {
- setStates(states, false);
- }
-
- private void setStates(final Collection<KeyedAppState> states, final boolean immediate) {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (states.isEmpty()) {
- return;
- }
-
- unbindOldBindings();
- bind();
-
- send(buildStatesBundle(states), immediate);
- }
- });
- }
+ public abstract void setStates(@NonNull Collection<KeyedAppState> states);
/**
* Performs the same function as {@link #setStates(Collection)}, except it
@@ -229,150 +120,5 @@
* <p>The receiver is not obligated to meet this immediate upload request.
* For example, Play and Android Management APIs have daily quotas.
*/
- public void setStatesImmediate(@NonNull Collection<KeyedAppState> states) {
- setStates(states, true);
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void bind() {
- Collection<String> acceptablePackageNames = getDeviceOwnerAndProfileOwnerPackageNames();
- acceptablePackageNames.add(PHONESKY_PACKAGE_NAME);
- bind(acceptablePackageNames);
- }
-
- private void bind(Collection<String> acceptablePackageNames) {
- // Remove already-bound packages
- Collection<String> filteredPackageNames = new HashSet<>();
- for (String packageName : acceptablePackageNames) {
- if (!mServiceConnections.containsKey(packageName)) {
- filteredPackageNames.add(packageName);
- }
- }
-
- if (filteredPackageNames.isEmpty()) {
- return;
- }
-
- Collection<ServiceInfo> serviceInfos =
- getServiceInfoInPackages(new Intent(ACTION_APP_STATES), filteredPackageNames);
-
- for (ServiceInfo serviceInfo : serviceInfos) {
- Intent bindIntent = new Intent();
- bindIntent.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
-
- BufferedServiceConnection bufferedServiceConnection =
- new BufferedServiceConnection(
- mExecutor, mContext, bindIntent, Context.BIND_AUTO_CREATE);
- bufferedServiceConnection.bindService();
-
- mServiceConnections.put(serviceInfo.packageName, bufferedServiceConnection);
- }
- }
-
- private Collection<String> getDeviceOwnerAndProfileOwnerPackageNames() {
- DevicePolicyManager devicePolicyManager =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- Collection<ComponentName> activeAdmins = devicePolicyManager.getActiveAdmins();
-
- if (activeAdmins == null) {
- return new ArrayList<>();
- }
-
- Collection<String> deviceOwnerProfileOwnerPackageNames = new ArrayList<>();
-
- for (ComponentName componentName : activeAdmins) {
- if (devicePolicyManager.isDeviceOwnerApp(componentName.getPackageName())
- || devicePolicyManager.isProfileOwnerApp(componentName.getPackageName())) {
- deviceOwnerProfileOwnerPackageNames.add(componentName.getPackageName());
- }
- }
-
- return deviceOwnerProfileOwnerPackageNames;
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void unbindOldBindings() {
- Iterator<Entry<String, BufferedServiceConnection>> iterator =
- mServiceConnections.entrySet().iterator();
-
- while (iterator.hasNext()) {
- Entry<String, BufferedServiceConnection> entry = iterator.next();
- if (packageNameShouldBeUnbound(entry.getKey())) {
- entry.getValue().unbind();
- iterator.remove();
- }
- }
- }
-
- /** Assumes the given package name is a stored service connection. */
- private boolean packageNameShouldBeUnbound(String packageName) {
- if (Build.VERSION.SDK_INT < 26
- && mServiceConnections.get(packageName).hasBeenDisconnected()) {
- return true;
- }
-
- if (mServiceConnections.get(packageName).isDead()) {
- return true;
- }
-
- if (!canPackageReceiveAppStates(mContext, packageName)) {
- return true;
- }
-
- return false;
- }
-
- static boolean canPackageReceiveAppStates(Context context, String packageName) {
- DevicePolicyManager devicePolicyManager =
- (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
-
- return packageName.equals(PHONESKY_PACKAGE_NAME)
- || devicePolicyManager.isDeviceOwnerApp(packageName)
- || devicePolicyManager.isProfileOwnerApp(packageName);
- }
-
- private Collection<ServiceInfo> getServiceInfoInPackages(
- Intent intent, Collection<String> acceptablePackageNames) {
- PackageManager packageManager = mContext.getPackageManager();
- List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, /* flags = */0);
-
- Collection<ServiceInfo> validServiceInfo = new ArrayList<>();
- for (ResolveInfo i : resolveInfos) {
- if (acceptablePackageNames.contains(i.serviceInfo.packageName)) {
- validServiceInfo.add(i.serviceInfo);
- }
- }
- return validServiceInfo;
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static Bundle buildStatesBundle(Collection<KeyedAppState> keyedAppStates) {
- Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(APP_STATES, buildStateBundles(keyedAppStates));
- return bundle;
- }
-
- // Returns an ArrayList as required to be used with Bundle#putParcelableArrayList.
- private static ArrayList<Bundle> buildStateBundles(Collection<KeyedAppState> keyedAppStates) {
- ArrayList<Bundle> bundles = new ArrayList<>();
- for (KeyedAppState keyedAppState : keyedAppStates) {
- bundles.add(keyedAppState.toStateBundle());
- }
- return bundles;
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void send(Bundle appStatesBundle, boolean immediate) {
- for (BufferedServiceConnection serviceConnection : mServiceConnections.values()) {
- // Messages cannot be reused so we create a copy for each service connection.
- serviceConnection.send(createStateMessage(appStatesBundle, immediate));
- }
- }
-
- private static Message createStateMessage(Bundle appStatesBundle, boolean immediate) {
- Message message = Message.obtain();
- message.what = immediate ? WHAT_IMMEDIATE_STATE : WHAT_STATE;
- message.obj = appStatesBundle;
- return message;
- }
+ public abstract void setStatesImmediate(@NonNull Collection<KeyedAppState> states);
}
diff --git a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/SingletonKeyedAppStatesReporter.java b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/SingletonKeyedAppStatesReporter.java
new file mode 100644
index 0000000..fba3232
--- /dev/null
+++ b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/SingletonKeyedAppStatesReporter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2019 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 androidx.enterprise.feedback;
+
+import android.annotation.SuppressLint;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link KeyedAppStatesReporter} that only allows a single instance to exist at one time,
+ * avoiding repeated instantiations.
+ */
+public class SingletonKeyedAppStatesReporter extends KeyedAppStatesReporter {
+
+ private static final String LOG_TAG = "KeyedAppStatesReporter";
+
+ @SuppressLint("StaticFieldLeak") // Application Context only.
+ private static volatile SingletonKeyedAppStatesReporter sSingleton;
+
+ private final Context mContext;
+
+ private final Map<String, BufferedServiceConnection> mServiceConnections = new HashMap<>();
+
+ private static final int EXECUTOR_IDLE_ALIVE_TIME_SECS = 20;
+ private final Executor mExecutor;
+
+ /**
+ * Creates an {@link ExecutorService} which has no persistent background thread, and ensures
+ * tasks will run in submit order.
+ */
+ private static ExecutorService createExecutorService() {
+ return new ThreadPoolExecutor(
+ /* corePoolSize= */ 0,
+ /* maximumPoolSize= */ 1,
+ EXECUTOR_IDLE_ALIVE_TIME_SECS,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>() /* Not used */);
+ }
+
+ /**
+ * Sets executor used to construct the singleton.
+ *
+ * <p>If required, this method must be called before calling {@link #getInstance(Context)}.
+ *
+ * <p>If this method is not called, the reporter will run on a newly-created thread.
+ * This newly-created thread will be cleaned up and recreated as necessary when idle.
+ */
+ public static void initialize(@NonNull Context context, @NonNull Executor executor) {
+ if (context == null || executor == null) {
+ throw new NullPointerException();
+ }
+ synchronized (KeyedAppStatesReporter.class) {
+ if (sSingleton != null) {
+ throw new IllegalStateException(
+ "initialize can only be called once and must be called before "
+ + "calling getInstance.");
+ }
+ initializeSingleton(context, executor);
+ }
+ }
+
+ /**
+ * Returns an instance of the reporter.
+ *
+ * <p>Creates and initializes an instance if one doesn't already exist.
+ */
+ @NonNull
+ public static KeyedAppStatesReporter getInstance(@NonNull Context context) {
+ if (context == null || context.getApplicationContext() == null) {
+ throw new NullPointerException();
+ }
+ if (sSingleton == null) {
+ synchronized (KeyedAppStatesReporter.class) {
+ if (sSingleton == null) {
+ initializeSingleton(context, createExecutorService());
+ }
+ }
+ }
+ return sSingleton;
+ }
+
+ private static void initializeSingleton(@NonNull Context context, @NonNull Executor executor) {
+ sSingleton = new SingletonKeyedAppStatesReporter(context, executor);
+ sSingleton.bind();
+ }
+
+ @VisibleForTesting
+ static void resetSingleton() {
+ synchronized (KeyedAppStatesReporter.class) {
+ sSingleton = null;
+ }
+ }
+
+ private SingletonKeyedAppStatesReporter(Context context, Executor executor) {
+ this.mContext = context.getApplicationContext();
+ this.mExecutor = executor;
+ }
+
+ @Override
+ public void setStates(@NonNull Collection<KeyedAppState> states) {
+ setStates(states, false);
+ }
+
+ private void setStates(final Collection<KeyedAppState> states, final boolean immediate) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (states.isEmpty()) {
+ return;
+ }
+
+ unbindOldBindings();
+ bind();
+
+ send(buildStatesBundle(states), immediate);
+ }
+ });
+ }
+
+ @Override
+ public void setStatesImmediate(@NonNull Collection<KeyedAppState> states) {
+ setStates(states, true);
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void bind() {
+ Collection<String> acceptablePackageNames = getDeviceOwnerAndProfileOwnerPackageNames();
+ acceptablePackageNames.add(PHONESKY_PACKAGE_NAME);
+ bind(acceptablePackageNames);
+ }
+
+ private void bind(Collection<String> acceptablePackageNames) {
+ // Remove already-bound packages
+ Collection<String> filteredPackageNames = new HashSet<>();
+ for (String packageName : acceptablePackageNames) {
+ if (!mServiceConnections.containsKey(packageName)) {
+ filteredPackageNames.add(packageName);
+ }
+ }
+
+ if (filteredPackageNames.isEmpty()) {
+ return;
+ }
+
+ Collection<ServiceInfo> serviceInfos =
+ getServiceInfoInPackages(new Intent(ACTION_APP_STATES), filteredPackageNames);
+
+ for (ServiceInfo serviceInfo : serviceInfos) {
+ Intent bindIntent = new Intent();
+ bindIntent.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
+
+ BufferedServiceConnection bufferedServiceConnection =
+ new BufferedServiceConnection(
+ mExecutor, mContext, bindIntent, Context.BIND_AUTO_CREATE);
+ bufferedServiceConnection.bindService();
+
+ mServiceConnections.put(serviceInfo.packageName, bufferedServiceConnection);
+ }
+ }
+
+ private Collection<String> getDeviceOwnerAndProfileOwnerPackageNames() {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ Collection<ComponentName> activeAdmins = devicePolicyManager.getActiveAdmins();
+
+ if (activeAdmins == null) {
+ return new ArrayList<>();
+ }
+
+ Collection<String> deviceOwnerProfileOwnerPackageNames = new ArrayList<>();
+
+ for (ComponentName componentName : activeAdmins) {
+ if (devicePolicyManager.isDeviceOwnerApp(componentName.getPackageName())
+ || devicePolicyManager.isProfileOwnerApp(componentName.getPackageName())) {
+ deviceOwnerProfileOwnerPackageNames.add(componentName.getPackageName());
+ }
+ }
+
+ return deviceOwnerProfileOwnerPackageNames;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void unbindOldBindings() {
+ Iterator<Entry<String, BufferedServiceConnection>> iterator =
+ mServiceConnections.entrySet().iterator();
+
+ while (iterator.hasNext()) {
+ Entry<String, BufferedServiceConnection> entry = iterator.next();
+ if (packageNameShouldBeUnbound(entry.getKey())) {
+ entry.getValue().unbind();
+ iterator.remove();
+ }
+ }
+ }
+
+ /** Assumes the given package name is a stored service connection. */
+ private boolean packageNameShouldBeUnbound(String packageName) {
+ if (Build.VERSION.SDK_INT < 26
+ && mServiceConnections.get(packageName).hasBeenDisconnected()) {
+ return true;
+ }
+
+ if (mServiceConnections.get(packageName).isDead()) {
+ return true;
+ }
+
+ if (!canPackageReceiveAppStates(mContext, packageName)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private Collection<ServiceInfo> getServiceInfoInPackages(
+ Intent intent, Collection<String> acceptablePackageNames) {
+ PackageManager packageManager = mContext.getPackageManager();
+ List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, /* flags = */0);
+
+ Collection<ServiceInfo> validServiceInfo = new ArrayList<>();
+ for (ResolveInfo i : resolveInfos) {
+ if (acceptablePackageNames.contains(i.serviceInfo.packageName)) {
+ validServiceInfo.add(i.serviceInfo);
+ }
+ }
+ return validServiceInfo;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ static Bundle buildStatesBundle(Collection<KeyedAppState> keyedAppStates) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(APP_STATES, buildStateBundles(keyedAppStates));
+ return bundle;
+ }
+
+ // Returns an ArrayList as required to be used with Bundle#putParcelableArrayList.
+ private static ArrayList<Bundle> buildStateBundles(Collection<KeyedAppState> keyedAppStates) {
+ ArrayList<Bundle> bundles = new ArrayList<>();
+ for (KeyedAppState keyedAppState : keyedAppStates) {
+ bundles.add(keyedAppState.toStateBundle());
+ }
+ return bundles;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void send(Bundle appStatesBundle, boolean immediate) {
+ for (BufferedServiceConnection serviceConnection : mServiceConnections.values()) {
+ // Messages cannot be reused so we create a copy for each service connection.
+ serviceConnection.send(createStateMessage(appStatesBundle, immediate));
+ }
+ }
+
+ private static Message createStateMessage(Bundle appStatesBundle, boolean immediate) {
+ Message message = Message.obtain();
+ message.what = immediate ? WHAT_IMMEDIATE_STATE : WHAT_STATE;
+ message.obj = appStatesBundle;
+ return message;
+ }
+}
diff --git a/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java b/enterprise/feedback/src/test/java/androidx/enterprise/feedback/SingletonKeyedAppStatesReporterTest.java
similarity index 94%
rename from enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java
rename to enterprise/feedback/src/test/java/androidx/enterprise/feedback/SingletonKeyedAppStatesReporterTest.java
index ccce0e4..1b88d1e 100644
--- a/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java
+++ b/enterprise/feedback/src/test/java/androidx/enterprise/feedback/SingletonKeyedAppStatesReporterTest.java
@@ -65,11 +65,11 @@
import java.util.Collections;
import java.util.concurrent.Executor;
-/** Tests {@link KeyedAppStatesReporter}. */
+/** Tests {@link SingletonKeyedAppStatesReporter}. */
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@Config(minSdk = 21)
-public class KeyedAppStatesReporterTest {
+public class SingletonKeyedAppStatesReporterTest {
private final ComponentName mTestComponentName = new ComponentName("test_package", "");
@@ -89,15 +89,15 @@
@Before
public void setUp() {
// Reset the singleton so tests are independent
- KeyedAppStatesReporter.resetSingleton();
+ SingletonKeyedAppStatesReporter.resetSingleton();
}
@Test
@SmallTest
public void getInstance_nullContext_throwsNullPointerException() {
- KeyedAppStatesReporter.resetSingleton();
+ SingletonKeyedAppStatesReporter.resetSingleton();
try {
- KeyedAppStatesReporter.getInstance(null);
+ SingletonKeyedAppStatesReporter.getInstance(null);
fail();
} catch (NullPointerException expected) {
}
@@ -106,11 +106,11 @@
@Test
@SmallTest
public void initialize_usesExecutor() {
- KeyedAppStatesReporter.resetSingleton();
+ SingletonKeyedAppStatesReporter.resetSingleton();
TestExecutor testExecutor = new TestExecutor();
- KeyedAppStatesReporter.initialize(mContext, testExecutor);
+ SingletonKeyedAppStatesReporter.initialize(mContext, testExecutor);
- KeyedAppStatesReporter.getInstance(mContext).setStates(singleton(mState));
+ SingletonKeyedAppStatesReporter.getInstance(mContext).setStates(singleton(mState));
assertThat(testExecutor.lastExecuted()).isNotNull();
}
@@ -118,11 +118,11 @@
@Test
@SmallTest
public void initialize_calledMultipleTimes_throwsIllegalStateException() {
- KeyedAppStatesReporter.resetSingleton();
- KeyedAppStatesReporter.initialize(mContext, mExecutor);
+ SingletonKeyedAppStatesReporter.resetSingleton();
+ SingletonKeyedAppStatesReporter.initialize(mContext, mExecutor);
try {
- KeyedAppStatesReporter.initialize(mContext, mExecutor);
+ SingletonKeyedAppStatesReporter.initialize(mContext, mExecutor);
} catch (IllegalStateException expected) {
}
}
@@ -130,11 +130,11 @@
@Test
@SmallTest
public void initialize_calledAfterGetInstance_throwsIllegalStateException() {
- KeyedAppStatesReporter.resetSingleton();
- KeyedAppStatesReporter.getInstance(mContext);
+ SingletonKeyedAppStatesReporter.resetSingleton();
+ SingletonKeyedAppStatesReporter.getInstance(mContext);
try {
- KeyedAppStatesReporter.initialize(mContext, mExecutor);
+ SingletonKeyedAppStatesReporter.initialize(mContext, mExecutor);
} catch (IllegalStateException expected) {
}
}
@@ -500,7 +500,7 @@
}
private KeyedAppStatesReporter getReporter(Context context) {
- KeyedAppStatesReporter.initialize(context, mExecutor);
- return KeyedAppStatesReporter.getInstance(context);
+ SingletonKeyedAppStatesReporter.initialize(context, mExecutor);
+ return SingletonKeyedAppStatesReporter.getInstance(context);
}
}
diff --git a/fragment/api/1.1.0-alpha09.txt b/fragment/api/1.1.0-alpha09.txt
index 97f531b..432303b 100644
--- a/fragment/api/1.1.0-alpha09.txt
+++ b/fragment/api/1.1.0-alpha09.txt
@@ -102,6 +102,7 @@
method @CallSuper public void onPause();
method public void onPictureInPictureModeChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
+ method public void onPrimaryNavigationFragmentChanged(boolean);
method public void onRequestPermissionsResult(int, String[], int[]);
method @CallSuper public void onResume();
method public void onSaveInstanceState(android.os.Bundle);
diff --git a/fragment/api/current.txt b/fragment/api/current.txt
index 97f531b..432303b 100644
--- a/fragment/api/current.txt
+++ b/fragment/api/current.txt
@@ -102,6 +102,7 @@
method @CallSuper public void onPause();
method public void onPictureInPictureModeChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
+ method public void onPrimaryNavigationFragmentChanged(boolean);
method public void onRequestPermissionsResult(int, String[], int[]);
method @CallSuper public void onResume();
method public void onSaveInstanceState(android.os.Bundle);
diff --git a/fragment/build.gradle b/fragment/build.gradle
index abe4c77..ee79ab7 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -19,7 +19,7 @@
api(project(":core")) {
exclude group: 'androidx.annotation'
}
- api(project(":collection"))
+ api("androidx.collection:collection:1.1.0-rc01")
api("androidx.viewpager:viewpager:1.0.0")
api("androidx.annotation:annotation:1.1.0-rc01")
api("androidx.loader:loader:1.0.0")
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
index b1ed36a..25a19c0 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
@@ -19,15 +19,18 @@
import android.app.Activity
import androidx.fragment.app.test.EmptyFragmentTestActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import androidx.test.filters.MediumTest
import androidx.test.rule.ActivityTestRule
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
-@SmallTest
+@MediumTest
class PrimaryNavFragmentTest {
@get:Rule
val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
@@ -35,7 +38,7 @@
@Test
fun delegateBackToPrimaryNav() {
val fm = activityRule.activity.supportFragmentManager
- val strictFragment = StrictFragment()
+ val strictFragment = spy(StrictFragment())
fm.beginTransaction()
.add(strictFragment, null)
@@ -43,6 +46,7 @@
.commit()
executePendingTransactions(fm)
+ verify(strictFragment).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("new fragment is not primary nav fragment")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment)
@@ -69,7 +73,9 @@
@Test
fun popPrimaryNav() {
val fm = activityRule.activity.supportFragmentManager
- val strictFragment1 = StrictFragment()
+ val strictFragment1 = spy(StrictFragment())
+ val strictFragment2 = spy(StrictFragment())
+ val inOrder = inOrder(strictFragment1, strictFragment2)
fm.beginTransaction()
.add(strictFragment1, null)
@@ -77,6 +83,7 @@
.commit()
executePendingTransactions(fm)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("new fragment is not primary nav fragment")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment1)
@@ -87,17 +94,18 @@
.commit()
executePendingTransactions(fm)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(false)
assertWithMessage("primary nav fragment is not null after remove")
.that(fm.primaryNavigationFragment)
.isNull()
activityRule.onBackPressed()
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("primary nav fragment was not restored on pop")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment1)
- val strictFragment2 = StrictFragment()
fm.beginTransaction()
.remove(strictFragment1)
.add(strictFragment2, null)
@@ -106,12 +114,16 @@
.commit()
executePendingTransactions(fm)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(false)
+ inOrder.verify(strictFragment2).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("primary nav fragment not updated to new fragment")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment2)
activityRule.onBackPressed()
+ inOrder.verify(strictFragment2).onPrimaryNavigationFragmentChanged(false)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("primary nav fragment not restored on pop")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment1)
@@ -136,7 +148,9 @@
@Test
fun replacePrimaryNav() {
val fm = activityRule.activity.supportFragmentManager
- val strictFragment1 = StrictFragment()
+ val strictFragment1 = spy(StrictFragment())
+ val strictFragment2 = spy(StrictFragment())
+ val inOrder = inOrder(strictFragment1, strictFragment2)
fm.beginTransaction()
.add(android.R.id.content, strictFragment1)
@@ -144,11 +158,11 @@
.commit()
executePendingTransactions(fm)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("new fragment is not primary nav fragment")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment1)
- val strictFragment2 = StrictFragment()
fm.beginTransaction()
.replace(android.R.id.content, strictFragment2)
.addToBackStack(null)
@@ -156,12 +170,14 @@
executePendingTransactions(fm)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(false)
assertWithMessage("primary nav fragment not null after replace")
.that(fm.primaryNavigationFragment)
.isNull()
activityRule.onBackPressed()
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("primary nav fragment not restored after popping replace")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment1)
@@ -171,6 +187,7 @@
.commit()
executePendingTransactions(fm)
+ inOrder.verify(strictFragment1).onPrimaryNavigationFragmentChanged(false)
assertWithMessage("primary nav fragment not null after explicit set to null")
.that(fm.primaryNavigationFragment)
.isNull()
@@ -182,12 +199,14 @@
.commit()
executePendingTransactions(fm)
+ inOrder.verify(strictFragment2).onPrimaryNavigationFragmentChanged(true)
assertWithMessage("primary nav fragment not set correctly after replace")
.that(fm.primaryNavigationFragment)
.isSameAs(strictFragment2)
activityRule.onBackPressed()
+ inOrder.verify(strictFragment2).onPrimaryNavigationFragmentChanged(false)
assertWithMessage("primary nav fragment not null after popping replace")
.that(fm.primaryNavigationFragment)
.isNull()
diff --git a/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 1b0313f..df869e7 100644
--- a/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -128,6 +128,9 @@
// Target request code.
int mTargetRequestCode;
+ // Boolean indicating whether this Fragment is the primary navigation fragment
+ private Boolean mIsPrimaryNavigationFragment = null;
+
// True if the fragment is in the list of added fragments.
boolean mAdded;
@@ -1814,6 +1817,20 @@
}
/**
+ * Callback for when the primary navigation state of this Fragment has changed. This can be
+ * the result of the {@link #getFragmentManager() containing FragmentManager} having its
+ * primary navigation fragment changed via
+ * {@link androidx.fragment.app.FragmentTransaction#setPrimaryNavigationFragment} or due to
+ * the primary navigation fragment changing in a parent FragmentManager.
+ *
+ * @param isPrimaryNavigationFragment True if and only if this Fragment and any
+ * {@link #getParentFragment() parent fragment} is set as the primary navigation fragment
+ * via {@link androidx.fragment.app.FragmentTransaction#setPrimaryNavigationFragment}.
+ */
+ public void onPrimaryNavigationFragmentChanged(boolean isPrimaryNavigationFragment) {
+ }
+
+ /**
* Called when the Fragment is no longer resumed. This is generally
* tied to {@link Activity#onPause() Activity.onPause} of the containing
* Activity's lifecycle.
@@ -2689,6 +2706,19 @@
}
}
+ void performPrimaryNavigationFragmentChanged() {
+ boolean isPrimaryNavigationFragment = mFragmentManager.isPrimaryNavigation(this);
+ // Only send out the callback / dispatch if the state has changed
+ if (mIsPrimaryNavigationFragment == null
+ || mIsPrimaryNavigationFragment != isPrimaryNavigationFragment) {
+ mIsPrimaryNavigationFragment = isPrimaryNavigationFragment;
+ onPrimaryNavigationFragmentChanged(isPrimaryNavigationFragment);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchPrimaryNavigationFragmentChanged();
+ }
+ }
+ }
+
void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
onMultiWindowModeChanged(isInMultiWindowMode);
if (mChildFragmentManager != null) {
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java b/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
index f0a8642..7a1532b 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
@@ -205,7 +205,7 @@
* navigation Fragments to ensure that all of the parent Fragments are the
* primary navigation Fragment for their associated FragmentManager
*/
- private boolean isPrimaryNavigation(@Nullable Fragment parent) {
+ boolean isPrimaryNavigation(@Nullable Fragment parent) {
// If the parent is null, then we're at the root host
// and we're always the primary navigation
if (parent == null) {
@@ -2528,7 +2528,7 @@
if (fms.mPrimaryNavActiveWho != null) {
mPrimaryNav = mActive.get(fms.mPrimaryNavActiveWho);
- dispatchOnParentPrimaryNavigationFragmentChanged(mPrimaryNav);
+ dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
}
this.mNextFragmentIndex = fms.mNextFragmentIndex;
}
@@ -2786,21 +2786,21 @@
}
Fragment previousPrimaryNav = mPrimaryNav;
mPrimaryNav = f;
- dispatchOnParentPrimaryNavigationFragmentChanged(previousPrimaryNav);
- dispatchOnParentPrimaryNavigationFragmentChanged(mPrimaryNav);
+ dispatchParentPrimaryNavigationFragmentChanged(previousPrimaryNav);
+ dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
}
- private void dispatchOnParentPrimaryNavigationFragmentChanged(@Nullable Fragment f) {
- if (f != null && f.mChildFragmentManager != null) {
- f.mChildFragmentManager.onParentPrimaryNavigationFragmentChanged();
+ private void dispatchParentPrimaryNavigationFragmentChanged(@Nullable Fragment f) {
+ if (f != null) {
+ f.performPrimaryNavigationFragmentChanged();
}
}
- private void onParentPrimaryNavigationFragmentChanged() {
+ void dispatchPrimaryNavigationFragmentChanged() {
updateOnBackPressedCallbackEnabled();
// Update all of our child Fragments with the new primary navigation state
for (Fragment f : mActive.values()) {
- dispatchOnParentPrimaryNavigationFragmentChanged(f);
+ dispatchParentPrimaryNavigationFragmentChanged(f);
}
}
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index 389e65a..2f3439a 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api(project(":vectordrawable"))
implementation("androidx.interpolator:interpolator:1.0.0")
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 5d92ddc..418f40e 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0-rc01")
api(project(":core"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/include-composite-deps.gradle b/include-composite-deps.gradle
index c8a795f..2ad98d0 100644
--- a/include-composite-deps.gradle
+++ b/include-composite-deps.gradle
@@ -28,7 +28,6 @@
File externalRoot = new File(buildScriptDir, '../../external')
includeBuild(new File(externalRoot, 'doclava'))
- includeBuild(new File(externalRoot, 'jdiff'))
}
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index 40c40f1..82b38f6 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -340,10 +340,6 @@
"to": "androidx/customview/widget/ViewDragHelper{0}"
},
{
- "from": "android/support/v4/widget/DrawerLayout(.*)",
- "to": "androidx/drawerlayout/widget/DrawerLayout{0}"
- },
- {
"from": "android/support/v4/util/ArrayMap(.*)",
"to": "androidx/collection/ArrayMap{0}"
},
@@ -678,6 +674,10 @@
"to": "ignore"
},
{
+ "from": "androidx/drawerlayout/widget/DrawerLayout(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/transition/(.*)",
"to": "ignore"
},
@@ -1120,7 +1120,7 @@
"to": "androidx/recommendation"
},
{
- "from": "android/support/drawerlayout",
+ "from": "androidx/drawerlayout",
"to": "androidx/drawerlayout"
},
{
@@ -2027,9 +2027,9 @@
},
{
"from": {
- "groupId": "com.android.support",
+ "groupId": "androidx.drawerlayout",
"artifactId": "drawerlayout",
- "version": "{oldSlVersion}"
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.drawerlayout",
@@ -4241,7 +4241,6 @@
"android/support/v4/widget/CompoundButtonCompat": "androidx/core/widget/CompoundButtonCompat",
"android/support/v4/widget/ContentLoadingProgressBar": "androidx/core/widget/ContentLoadingProgressBar",
"android/support/v4/widget/DirectedAcyclicGraph": "androidx/coordinatorlayout/widget/DirectedAcyclicGraph",
- "android/support/v4/widget/DrawerLayout": "androidx/drawerlayout/widget/DrawerLayout",
"android/support/v4/widget/EdgeEffectCompat": "androidx/core/widget/EdgeEffectCompat",
"android/support/v4/widget/ExploreByTouchHelper": "androidx/customview/widget/ExploreByTouchHelper",
"android/support/v4/widget/FocusStrategy": "androidx/customview/widget/FocusStrategy",
diff --git a/media2/session/src/androidTest/AndroidManifest.xml b/media2/session/src/androidTest/AndroidManifest.xml
index 874f321..3d49478 100644
--- a/media2/session/src/androidTest/AndroidManifest.xml
+++ b/media2/session/src/androidTest/AndroidManifest.xml
@@ -24,6 +24,7 @@
<application android:usesCleartextTraffic="true">
<activity android:name="androidx.media2.session.MockActivity" />
+ <activity android:name="androidx.media2.session.SurfaceActivity" />
<!-- Keep the test services synced together with the MockMediaSessionService -->
<service android:name="androidx.media2.session.MockMediaSessionService">
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java b/media2/session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
new file mode 100644
index 0000000..043989a
--- /dev/null
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MediaController_SurfaceTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2019 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 androidx.media2.session;
+
+import static android.content.Context.KEYGUARD_SERVICE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.os.Build;
+import android.os.Process;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaController#setSurface(Surface)}.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaController_SurfaceTest extends MediaSessionTestBase {
+ private static final String TAG = "MC_SurfaceTest";
+
+ private Instrumentation mInstrumentation;
+ private SurfaceActivity mActivity;
+ private MockPlayer mPlayer;
+ private MediaSession mSession;
+ private MediaController mController;
+
+ @Rule
+ public ActivityTestRule<SurfaceActivity> mActivityRule =
+ new ActivityTestRule<>(SurfaceActivity.class);
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+
+ setKeepScreenOn();
+
+ mPlayer = new MockPlayer(0);
+ mSession = new MediaSession.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, new MediaSession.SessionCallback() {
+ @Override
+ public SessionCommandGroup onConnect(
+ @NonNull MediaSession session,
+ @NonNull MediaSession.ControllerInfo controller) {
+ if (Process.myUid() == controller.getUid()) {
+ return super.onConnect(session, controller);
+ }
+ return null;
+ }
+ })
+ .setId(TAG)
+ .build();
+ mController = createController(mSession.getToken());
+ }
+
+ @After
+ @Override
+ public void cleanUp() throws Exception {
+ super.cleanUp();
+
+ mSession.close();
+ }
+
+ @Test
+ public void testSetSurface() throws Exception {
+ prepareLooper();
+
+ // Set
+ final Surface testSurface = mActivity.getSurfaceHolder().getSurface();
+ SessionResult result = mController.setSurface(testSurface)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
+ assertSame(testSurface, mPlayer.mSurface);
+
+ // Reset
+ result = mController.setSurface(null).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
+ assertNull(mPlayer.mSurface);
+ }
+
+ private void setKeepScreenOn() throws Exception {
+ try {
+ setKeepScreenOnOrThrow();
+ } catch (Throwable tr) {
+ throw new Exception(tr);
+ }
+ }
+
+ private void setKeepScreenOnOrThrow() throws Throwable {
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (Build.VERSION.SDK_INT >= 27) {
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mActivity.setTurnScreenOn(true);
+ mActivity.setShowWhenLocked(true);
+ KeyguardManager keyguardManager = (KeyguardManager)
+ mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
+ keyguardManager.requestDismissKeyguard(mActivity, null);
+ } else {
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+}
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java b/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java
index a7b9b9e..26b4406 100644
--- a/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java
+++ b/media2/session/src/androidTest/java/androidx/media2/session/MockPlayer.java
@@ -16,7 +16,10 @@
package androidx.media2.session;
+import android.view.Surface;
+
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.media.AudioAttributesCompat;
import androidx.media2.common.MediaItem;
@@ -62,6 +65,7 @@
public @RepeatMode int mRepeatMode = -1;
public @ShuffleMode int mShuffleMode = -1;
public VideoSize mVideoSize = new VideoSize(0, 0);
+ public Surface mSurface;
public boolean mSetPlaylistCalled;
public boolean mUpdatePlaylistMetadataCalled;
@@ -491,4 +495,11 @@
});
}
}
+
+ @Override
+ @NonNull
+ public ListenableFuture<PlayerResult> setSurfaceInternal(@Nullable Surface surface) {
+ mSurface = surface;
+ return new SyncListenableFuture(mCurrentMediaItem);
+ }
}
diff --git a/media2/session/src/androidTest/java/androidx/media2/session/SurfaceActivity.java b/media2/session/src/androidTest/java/androidx/media2/session/SurfaceActivity.java
new file mode 100644
index 0000000..480d92b
--- /dev/null
+++ b/media2/session/src/androidTest/java/androidx/media2/session/SurfaceActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 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 androidx.media2.session;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import androidx.media2.session.test.R;
+
+public class SurfaceActivity extends Activity {
+ private SurfaceHolder mHolder;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_surface);
+
+ SurfaceView surfaceView = findViewById(R.id.surface_view);
+ mHolder = surfaceView.getHolder();
+ }
+
+ public SurfaceHolder getSurfaceHolder() {
+ return mHolder;
+ }
+}
diff --git a/media2/session/src/androidTest/res/layout/activity_surface.xml b/media2/session/src/androidTest/res/layout/activity_surface.xml
new file mode 100644
index 0000000..7945f97
--- /dev/null
+++ b/media2/session/src/androidTest/res/layout/activity_surface.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2019 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:keepScreenOn="true">
+ <SurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ </SurfaceView>
+</LinearLayout>
diff --git a/media2/session/src/main/aidl/androidx/media2/session/IMediaSession.aidl b/media2/session/src/main/aidl/androidx/media2/session/IMediaSession.aidl
index 604ca65..0c55f331 100644
--- a/media2/session/src/main/aidl/androidx/media2/session/IMediaSession.aidl
+++ b/media2/session/src/main/aidl/androidx/media2/session/IMediaSession.aidl
@@ -18,6 +18,7 @@
import android.os.Bundle;
import android.net.Uri;
+import android.view.Surface;
import androidx.media2.common.ParcelImplListSlice;
import androidx.media2.session.IMediaController;
@@ -69,6 +70,7 @@
void skipToNextItem(IMediaController caller, int seq) = 29;
void setRepeatMode(IMediaController caller, int seq, int repeatMode) = 30;
void setShuffleMode(IMediaController caller, int seq, int shuffleMode) = 31;
+ void setSurface(IMediaController caller, int seq, in Surface surface) = 40;
void onControllerResult(IMediaController caller, int seq,
in ParcelImpl controllerResult) = 32;
@@ -86,5 +88,5 @@
void subscribe(IMediaController caller, int seq, String parentId,
in ParcelImpl libraryParams) = 38;
void unsubscribe(IMediaController caller, int seq, String parentId) = 39;
- // Next Id : 40
+ // Next Id : 41
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaBrowser.java b/media2/session/src/main/java/androidx/media2/session/MediaBrowser.java
index 617b53f..fe4dd9f 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaBrowser.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaBrowser.java
@@ -96,14 +96,11 @@
@Override
MediaBrowserImpl createImpl(@NonNull Context context, @NonNull SessionToken token,
- @Nullable Bundle connectionHints, @Nullable Executor executor,
- @Nullable ControllerCallback callback) {
+ @Nullable Bundle connectionHints) {
if (token.isLegacySession()) {
- return new MediaBrowserImplLegacy(
- context, this, token, executor, (BrowserCallback) callback);
+ return new MediaBrowserImplLegacy(context, this, token);
} else {
- return new MediaBrowserImplBase(context, this, token, connectionHints,
- executor, (BrowserCallback) callback);
+ return new MediaBrowserImplBase(context, this, token, connectionHints);
}
}
@@ -112,11 +109,6 @@
return (MediaBrowserImpl) super.getImpl();
}
- @Override
- BrowserCallback getCallback() {
- return (BrowserCallback) super.getCallback();
- }
-
/**
* Gets the library root.
* <p>
@@ -286,6 +278,20 @@
return createDisconnectedFuture();
}
+ void notifyBrowserCallback(final BrowserCallbackRunnable callbackRunnable) {
+ if (mCallback != null && mCallbackExecutor != null) {
+ mCallbackExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ callbackRunnable.run((BrowserCallback) mCallback);
+ }
+ });
+ }
+ }
+
+ interface BrowserCallbackRunnable {
+ void run(@NonNull BrowserCallback callback);
+ }
/**
* Builder for {@link MediaBrowser}.
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java
index a0dbda4..30affc9 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplBase.java
@@ -32,16 +32,16 @@
import android.os.RemoteException;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media2.common.MediaParcelUtils;
import androidx.media2.session.MediaBrowser.BrowserCallback;
+import androidx.media2.session.MediaBrowser.BrowserCallbackRunnable;
import androidx.media2.session.MediaLibraryService.LibraryParams;
import androidx.media2.session.SequencedFutureManager.SequencedFuture;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.concurrent.Executor;
-
/**
* Base implementation of MediaBrowser.
*/
@@ -51,18 +51,13 @@
new LibraryResult(RESULT_INFO_SKIPPED);
MediaBrowserImplBase(Context context, MediaController instance, SessionToken token,
- @Nullable Bundle connectionHints, Executor executor, BrowserCallback callback) {
- super(context, instance, token, connectionHints, executor, callback);
+ @Nullable Bundle connectionHints) {
+ super(context, instance, token, connectionHints);
}
- @Override
- public MediaBrowser.BrowserCallback getCallback() {
- return (MediaBrowser.BrowserCallback) super.getCallback();
- }
-
- @Override
- public MediaBrowser getInstance() {
- return (MediaBrowser) super.getInstance();
+ @NonNull
+ MediaBrowser getMediaBrowser() {
+ return (MediaBrowser) mInstance;
}
@Override
@@ -153,22 +148,20 @@
void notifySearchResultChanged(final String query, final int itemCount,
final LibraryParams libraryParams) {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
@Override
- public void run() {
- getCallback().onSearchResultChanged(getInstance(), query, itemCount, libraryParams);
+ public void run(@NonNull BrowserCallback callback) {
+ callback.onSearchResultChanged(getMediaBrowser(), query, itemCount, libraryParams);
}
});
}
void notifyChildrenChanged(final String parentId, final int itemCount,
final LibraryParams libraryParams) {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
@Override
- public void run() {
- getCallback().onChildrenChanged(getInstance(), parentId, itemCount, libraryParams);
+ public void run(@NonNull BrowserCallback callback) {
+ callback.onChildrenChanged(getMediaBrowser(), parentId, itemCount, libraryParams);
}
});
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java b/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java
index 8c3ae11..0b03604 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaBrowserImplLegacy.java
@@ -39,6 +39,8 @@
import androidx.concurrent.futures.ResolvableFuture;
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
+import androidx.media2.session.MediaBrowser.BrowserCallback;
+import androidx.media2.session.MediaBrowser.BrowserCallbackRunnable;
import androidx.media2.session.MediaLibraryService.LibraryParams;
import com.google.common.util.concurrent.ListenableFuture;
@@ -46,7 +48,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support.
@@ -62,14 +63,13 @@
private final HashMap<String, List<SubscribeCallback>> mSubscribeCallbacks = new HashMap<>();
MediaBrowserImplLegacy(@NonNull Context context, MediaBrowser instance,
- @NonNull SessionToken token, @Nullable /*@CallbackExecutor*/ Executor executor,
- @Nullable MediaBrowser.BrowserCallback callback) {
- super(context, instance, token, executor, callback);
+ @NonNull SessionToken token) {
+ super(context, instance, token);
}
- @Override
- public MediaBrowser getInstance() {
- return (MediaBrowser) super.getInstance();
+ @NonNull
+ MediaBrowser getMediaBrowser() {
+ return (MediaBrowser) mInstance;
}
@Override
@@ -219,33 +219,31 @@
@Override
public void onSearchResult(final String query, final Bundle extras,
final List<MediaBrowserCompat.MediaItem> items) {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull BrowserCallback callback) {
// Set extra null here, because 'extra' have different meanings between old
// API and new API as follows.
// - Old API: Extra/Option specified with search().
// - New API: Extra from MediaLibraryService to MediaBrowser
// TODO(Post-P): Cache search result for later getSearchResult() calls.
- getCallback().onSearchResultChanged(
- getInstance(), query, items.size(), null);
+ callback.onSearchResultChanged(
+ getMediaBrowser(), query, items.size(), null);
}
});
}
@Override
public void onError(final String query, final Bundle extras) {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull BrowserCallback callback) {
// Set extra null here, because 'extra' have different meanings between old
// API and new API as follows.
// - Old API: Extra/Option specified with search().
// - New API: Extra from MediaLibraryService to MediaBrowser
- getCallback().onSearchResultChanged(
- getInstance(), query, 0, null);
+ callback.onSearchResultChanged(
+ getMediaBrowser(), query, 0, null);
}
});
}
@@ -293,11 +291,6 @@
return future;
}
- @Override
- public MediaBrowser.BrowserCallback getCallback() {
- return (MediaBrowser.BrowserCallback) super.getCallback();
- }
-
private MediaBrowserCompat getBrowserCompat(LibraryParams extras) {
synchronized (mLock) {
return mBrowserCompats.get(extras);
@@ -405,12 +398,11 @@
final LibraryParams params = MediaUtils.convertToLibraryParams(mContext,
browserCompat.getNotifyChildrenChangedOptions());
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ getMediaBrowser().notifyBrowserCallback(new BrowserCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull BrowserCallback callback) {
// TODO(Post-P): Cache children result for later getChildren() calls.
- getCallback().onChildrenChanged(getInstance(), parentId, itemCount, params);
+ callback.onChildrenChanged(getMediaBrowser(), parentId, itemCount, params);
}
});
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaController.java b/media2/session/src/main/java/androidx/media2/session/MediaController.java
index 92a2d3c..327f0a4 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaController.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaController.java
@@ -33,6 +33,7 @@
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.text.TextUtils;
+import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
@@ -126,6 +127,9 @@
@GuardedBy("mLock")
boolean mClosed;
+ final ControllerCallback mCallback;
+ final Executor mCallbackExecutor;
+
// For testing.
Long mTimeDiff;
@@ -146,8 +150,10 @@
if (token == null) {
throw new NullPointerException("token shouldn't be null");
}
+ mCallback = callback;
+ mCallbackExecutor = executor;
synchronized (mLock) {
- mImpl = createImpl(context, token, connectionHints, executor, callback);
+ mImpl = createImpl(context, token, connectionHints);
}
}
@@ -168,6 +174,8 @@
if (token == null) {
throw new NullPointerException("token shouldn't be null");
}
+ mCallback = callback;
+ mCallbackExecutor = executor;
SessionToken.createSessionToken(context, token, executor,
new SessionToken.OnSessionTokenCreatedListener() {
@Override
@@ -175,12 +183,11 @@
SessionToken token2) {
synchronized (mLock) {
if (!mClosed) {
- mImpl = createImpl(context, token2, connectionHints, executor,
- callback);
+ mImpl = createImpl(context, token2, connectionHints);
} else {
- executor.execute(new Runnable() {
+ notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
callback.onDisconnected(MediaController.this);
}
});
@@ -191,13 +198,11 @@
}
MediaControllerImpl createImpl(@NonNull Context context, @NonNull SessionToken token,
- @Nullable Bundle connectionHints, @Nullable Executor executor,
- @Nullable ControllerCallback callback) {
+ @Nullable Bundle connectionHints) {
if (token.isLegacySession()) {
- return new MediaControllerImplLegacy(context, this, token, executor, callback);
+ return new MediaControllerImplLegacy(context, this, token);
} else {
- return new MediaControllerImplBase(context, this, token, connectionHints, executor,
- callback);
+ return new MediaControllerImplBase(context, this, token, connectionHints);
}
}
@@ -1086,6 +1091,24 @@
}
/**
+ * Sets the {@link Surface} to be used as the sink for the video portion of the media.
+ * <p>
+ * This calls {@link SessionPlayer#setSurfaceInternal(Surface)}.
+ *
+ * @param surface The {@link Surface} to be used for the video portion of the media.
+ * @return a {@link ListenableFuture} which represents the pending completion of the command.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public ListenableFuture<SessionResult> setSurface(@Nullable Surface surface) {
+ if (isConnected()) {
+ return getImpl().setSurface(surface);
+ }
+ return createDisconnectedFuture();
+ }
+
+ /**
* Sets the time diff forcefully when calculating current position.
* @param timeDiff {@code null} for reset.
*
@@ -1101,14 +1124,19 @@
SessionResult.RESULT_ERROR_SESSION_DISCONNECTED);
}
- @Nullable
- ControllerCallback getCallback() {
- return isConnected() ? getImpl().getCallback() : null;
+ void notifyControllerCallback(final ControllerCallbackRunnable callbackRunnable) {
+ if (mCallback != null && mCallbackExecutor != null) {
+ mCallbackExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ callbackRunnable.run(mCallback);
+ }
+ });
+ }
}
- @Nullable
- Executor getCallbackExecutor() {
- return isConnected() ? getImpl().getCallbackExecutor() : null;
+ interface ControllerCallbackRunnable {
+ void run(@NonNull ControllerCallback callback);
}
interface MediaControllerImpl extends AutoCloseable {
@@ -1172,12 +1200,10 @@
@ShuffleMode int getShuffleMode();
ListenableFuture<SessionResult> setShuffleMode(@ShuffleMode int shuffleMode);
@NonNull VideoSize getVideoSize();
+ ListenableFuture<SessionResult> setSurface(@Nullable Surface surface);
// Internally used methods
- @NonNull MediaController getInstance();
@NonNull Context getContext();
- @Nullable ControllerCallback getCallback();
- @Nullable Executor getCallbackExecutor();
@Nullable MediaBrowserCompat getBrowserCompat();
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
index e81e24bc..cd6905d 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
@@ -32,6 +32,7 @@
import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_REPEAT_MODE;
import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SHUFFLE_MODE;
import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED;
+import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SURFACE;
import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM;
import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM;
import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM;
@@ -68,6 +69,7 @@
import android.os.SystemClock;
import android.support.v4.media.MediaBrowserCompat;
import android.util.Log;
+import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -81,6 +83,7 @@
import androidx.media2.common.SessionPlayer.ShuffleMode;
import androidx.media2.common.VideoSize;
import androidx.media2.session.MediaController.ControllerCallback;
+import androidx.media2.session.MediaController.ControllerCallbackRunnable;
import androidx.media2.session.MediaController.MediaControllerImpl;
import androidx.media2.session.MediaController.PlaybackInfo;
import androidx.media2.session.MediaController.VolumeDirection;
@@ -89,7 +92,6 @@
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
-import java.util.concurrent.Executor;
class MediaControllerImplBase implements MediaControllerImpl {
private static final boolean THROW_EXCEPTION_FOR_NULL_RESULT = true;
@@ -106,10 +108,6 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
final SessionToken mToken;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ControllerCallback mCallback;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Executor mCallbackExecutor;
private final IBinder.DeathRecipient mDeathRecipient;
final SequencedFutureManager mSequencedFutureManager;
final MediaControllerStub mControllerStub;
@@ -163,7 +161,7 @@
private volatile IMediaSession mISession;
MediaControllerImplBase(Context context, MediaController instance, SessionToken token,
- @Nullable Bundle connectionHints, Executor executor, ControllerCallback callback) {
+ @Nullable Bundle connectionHints) {
mInstance = instance;
if (context == null) {
throw new NullPointerException("context shouldn't be null");
@@ -175,8 +173,6 @@
mSequencedFutureManager = new SequencedFutureManager();
mControllerStub = new MediaControllerStub(this, mSequencedFutureManager);
mToken = token;
- mCallback = callback;
- mCallbackExecutor = executor;
mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -228,11 +224,10 @@
}
}
mSequencedFutureManager.close();
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onDisconnected(mInstance);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onDisconnected(mInstance);
}
});
}
@@ -781,6 +776,17 @@
}
@Override
+ public ListenableFuture<SessionResult> setSurface(final @Nullable Surface surface) {
+ return dispatchRemoteSessionTask(COMMAND_CODE_PLAYER_SET_SURFACE,
+ new RemoteSessionTask() {
+ @Override
+ public void run(IMediaSession iSession, int seq) throws RemoteException {
+ iSession.setSurface(mControllerStub, seq, surface);
+ }
+ });
+ }
+
+ @Override
@NonNull
public Context getContext() {
return mContext;
@@ -788,28 +794,10 @@
@Override
@Nullable
- public ControllerCallback getCallback() {
- return mCallback;
- }
-
- @Override
- @Nullable
- public Executor getCallbackExecutor() {
- return mCallbackExecutor;
- }
-
- @Override
- @Nullable
public MediaBrowserCompat getBrowserCompat() {
return null;
}
- @Override
- @NonNull
- public MediaController getInstance() {
- return mInstance;
- }
-
private boolean requestConnectToService() {
// Service. Needs to get fresh binder whenever connection is needed.
final Intent intent = new Intent(MediaSessionService.SERVICE_INTERFACE);
@@ -893,14 +881,13 @@
mPlaylist.set(currentMediaItemIndex, item);
}
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onCurrentMediaItemChanged(mInstance, item);
+ callback.onCurrentMediaItemChanged(mInstance, item);
}
});
}
@@ -911,14 +898,13 @@
mPositionMs = positionMs;
mPlayerState = state;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlayerStateChanged(mInstance, state);
+ callback.onPlayerStateChanged(mInstance, state);
}
});
}
@@ -929,14 +915,13 @@
mPositionMs = positionMs;
mPlaybackSpeed = speed;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaybackSpeedChanged(mInstance, speed);
+ callback.onPlaybackSpeedChanged(mInstance, speed);
}
});
}
@@ -949,14 +934,13 @@
mPositionEventTimeMs = eventTimeMs;
mPositionMs = positionMs;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onBufferingStateChanged(mInstance, item, state);
+ callback.onBufferingStateChanged(mInstance, item, state);
}
});
}
@@ -973,14 +957,13 @@
mCurrentMediaItem = playlist.get(currentMediaItemIndex);
}
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaylistChanged(mInstance, playlist, metadata);
+ callback.onPlaylistChanged(mInstance, playlist, metadata);
}
});
}
@@ -989,14 +972,13 @@
synchronized (mLock) {
mPlaylistMetadata = metadata;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaylistMetadataChanged(mInstance, metadata);
+ callback.onPlaylistMetadataChanged(mInstance, metadata);
}
});
}
@@ -1005,14 +987,13 @@
synchronized (mLock) {
mPlaybackInfo = info;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaybackInfoChanged(mInstance, info);
+ callback.onPlaybackInfoChanged(mInstance, info);
}
});
}
@@ -1025,14 +1006,13 @@
mPreviousMediaItemIndex = previousMediaItemIndex;
mNextMediaItemIndex = nextMediaItemIndex;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onRepeatModeChanged(mInstance, repeatMode);
+ callback.onRepeatModeChanged(mInstance, repeatMode);
}
});
}
@@ -1045,27 +1025,25 @@
mPreviousMediaItemIndex = previousMediaItemIndex;
mNextMediaItemIndex = nextMediaItemIndex;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onShuffleModeChanged(mInstance, shuffleMode);
+ callback.onShuffleModeChanged(mInstance, shuffleMode);
}
});
}
void notifyPlaybackCompleted() {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaybackCompleted(mInstance);
+ callback.onPlaybackCompleted(mInstance);
}
});
}
@@ -1076,14 +1054,13 @@
mPositionMs = positionMs;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onSeekCompleted(mInstance, seekPositionMs);
+ callback.onSeekCompleted(mInstance, seekPositionMs);
}
});
}
@@ -1092,14 +1069,13 @@
synchronized (mLock) {
mVideoSize = videoSize;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onVideoSizeChanged(mInstance, item, videoSize);
+ callback.onVideoSizeChanged(mInstance, item, videoSize);
}
});
}
@@ -1177,11 +1153,10 @@
mToken.getUid(), TYPE_SESSION, mToken.getPackageName(), sessionBinder,
tokenExtras));
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onConnected(mInstance, allowedCommands);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onConnected(mInstance, allowedCommands);
}
});
} finally {
@@ -1214,11 +1189,10 @@
if (DEBUG) {
Log.d(TAG, "onCustomCommand cmd=" + command.getCustomAction());
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- SessionResult result = mCallback.onCustomCommand(mInstance, command, args);
+ public void run(@NonNull ControllerCallback callback) {
+ SessionResult result = callback.onCustomCommand(mInstance, command, args);
if (result == null) {
if (THROW_EXCEPTION_FOR_NULL_RESULT) {
throw new RuntimeException("ControllerCallback#onCustomCommand() has"
@@ -1233,21 +1207,19 @@
}
void onAllowedCommandsChanged(final SessionCommandGroup commands) {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onAllowedCommandsChanged(mInstance, commands);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onAllowedCommandsChanged(mInstance, commands);
}
});
}
void onSetCustomLayout(final int seq, final List<MediaSession.CommandButton> layout) {
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- int resultCode = mCallback.onSetCustomLayout(mInstance, layout);
+ public void run(@NonNull ControllerCallback callback) {
+ int resultCode = callback.onSetCustomLayout(mInstance, layout);
SessionResult result = new SessionResult(resultCode);
sendControllerResult(seq, result);
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
index 50eef62..d85d75b 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
@@ -52,6 +52,7 @@
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
import android.util.Log;
+import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -68,6 +69,7 @@
import androidx.media2.common.SessionPlayer.ShuffleMode;
import androidx.media2.common.VideoSize;
import androidx.media2.session.MediaController.ControllerCallback;
+import androidx.media2.session.MediaController.ControllerCallbackRunnable;
import androidx.media2.session.MediaController.PlaybackInfo;
import androidx.media2.session.MediaController.VolumeDirection;
import androidx.media2.session.MediaController.VolumeFlags;
@@ -77,7 +79,6 @@
import java.util.List;
import java.util.Set;
-import java.util.concurrent.Executor;
// TODO: Find better way to return listenable future.
@SuppressLint("ObsoleteSdkInt") // TODO: Remove once the minSdkVersion is lowered enough.
@@ -103,10 +104,6 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
final SessionToken mToken;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ControllerCallback mCallback;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Executor mCallbackExecutor;
@SuppressWarnings("WeakerAccess") /* synthetic access */
final HandlerThread mHandlerThread;
@@ -184,16 +181,13 @@
private volatile boolean mConnected;
MediaControllerImplLegacy(@NonNull Context context, @NonNull MediaController instance,
- @NonNull SessionToken token, @Nullable Executor executor,
- @Nullable ControllerCallback callback) {
+ @NonNull SessionToken token) {
mContext = context;
mInstance = instance;
mHandlerThread = new HandlerThread("MediaController_Thread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mToken = token;
- mCallback = callback;
- mCallbackExecutor = executor;
if (mToken.getType() == SessionToken.TYPE_SESSION) {
synchronized (mLock) {
@@ -235,11 +229,10 @@
}
mConnected = false;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onDisconnected(mInstance);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onDisconnected(mInstance);
}
});
}
@@ -810,32 +803,23 @@
}
@Override
+ public ListenableFuture<SessionResult> setSurface(@Nullable Surface surface) {
+ Log.w(TAG, "Session doesn't support setting Surface");
+ return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
+ }
+
+ @Override
public @NonNull Context getContext() {
return mContext;
}
@Override
- public @NonNull ControllerCallback getCallback() {
- return mCallback;
- }
-
- @Override
- public @NonNull Executor getCallbackExecutor() {
- return mCallbackExecutor;
- }
-
- @Override
public @Nullable MediaBrowserCompat getBrowserCompat() {
synchronized (mLock) {
return mBrowserCompat;
}
}
- @Override
- public @NonNull MediaController getInstance() {
- return mInstance;
- }
-
// Should be used without a lock to prevent potential deadlock.
void onConnectedNotLocked() {
if (DEBUG) {
@@ -880,18 +864,17 @@
setCurrentMediaItemLocked(mControllerCompat.getMetadata());
mConnected = true;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onConnected(mInstance, allowedCommands);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onConnected(mInstance, allowedCommands);
}
});
if (!customLayout.isEmpty()) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onSetCustomLayout(mInstance, customLayout);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onSetCustomLayout(mInstance, customLayout);
}
});
}
@@ -1073,12 +1056,11 @@
return;
}
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
// Ignore return because legacy session cannot get result back.
- mCallback.onCustomCommand(mInstance, new SessionCommand(event, null), extras);
+ callback.onCustomCommand(mInstance, new SessionCommand(event, null), extras);
}
});
}
@@ -1123,41 +1105,41 @@
currentAllowedCommands = mAllowedCommands;
}
- if (mCallback == null) return;
+ if (mInstance.mCallback == null) return;
if (prevItem != currentItem) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onCurrentMediaItemChanged(mInstance, currentItem);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onCurrentMediaItemChanged(mInstance, currentItem);
}
});
}
if (state == null) {
if (prevState != null) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onPlayerStateChanged(mInstance, PLAYER_STATE_IDLE);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onPlayerStateChanged(mInstance, PLAYER_STATE_IDLE);
}
});
}
return;
}
if (prevState == null || prevState.getState() != state.getState()) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onPlayerStateChanged(
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onPlayerStateChanged(
mInstance, MediaUtils.convertToPlayerState(state));
}
});
}
if (prevState == null || prevState.getPlaybackSpeed() != state.getPlaybackSpeed()) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onPlaybackSpeedChanged(mInstance, state.getPlaybackSpeed());
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onPlaybackSpeedChanged(mInstance, state.getPlaybackSpeed());
}
});
}
@@ -1167,10 +1149,10 @@
long positionDiff = Math.abs(currentPosition
- prevState.getCurrentPosition(mInstance.mTimeDiff));
if (positionDiff > POSITION_DIFF_TOLERANCE) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onSeekCompleted(mInstance, currentPosition);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onSeekCompleted(mInstance, currentPosition);
}
});
}
@@ -1180,10 +1162,10 @@
Set<SessionCommand> currentCommands = currentAllowedCommands.getCommands();
if (prevCommands.size() != currentCommands.size()
|| !prevCommands.containsAll(currentCommands)) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onAllowedCommandsChanged(mInstance, currentAllowedCommands);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onAllowedCommandsChanged(mInstance, currentAllowedCommands);
}
});
}
@@ -1201,10 +1183,10 @@
layoutChanged = true;
}
if (layoutChanged) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onSetCustomLayout(mInstance, currentLayout);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onSetCustomLayout(mInstance, currentLayout);
}
});
}
@@ -1218,10 +1200,10 @@
? SessionPlayer.BUFFERING_STATE_UNKNOWN
: MediaUtils.toBufferingState(prevState.getState());
if (bufferingState != prevBufferingState) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onBufferingStateChanged(mInstance, currentItem, bufferingState);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onBufferingStateChanged(mInstance, currentItem, bufferingState);
}
});
}
@@ -1239,12 +1221,11 @@
setCurrentMediaItemLocked(metadata);
currentItem = mCurrentMediaItem;
}
- if (mCallback == null) return;
if (prevItem != currentItem) {
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onCurrentMediaItemChanged(mInstance, currentItem);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onCurrentMediaItemChanged(mInstance, currentItem);
}
});
}
@@ -1271,11 +1252,10 @@
playlist = mPlaylist;
playlistMetadata = mPlaylistMetadata;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onPlaylistChanged(mInstance, playlist, playlistMetadata);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onPlaylistChanged(mInstance, playlist, playlistMetadata);
}
});
}
@@ -1290,11 +1270,10 @@
mPlaylistMetadata = MediaUtils.convertToMediaMetadata(title);
playlistMetadata = mPlaylistMetadata;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onPlaylistMetadataChanged(mInstance, playlistMetadata);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onPlaylistMetadataChanged(mInstance, playlistMetadata);
}
});
}
@@ -1306,11 +1285,10 @@
return;
}
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onCustomCommand(mInstance, new SessionCommand(
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onCustomCommand(mInstance, new SessionCommand(
SESSION_COMMAND_ON_EXTRAS_CHANGED, null), extras);
}
});
@@ -1323,11 +1301,10 @@
return;
}
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onPlaybackInfoChanged(mInstance, MediaUtils.toPlaybackInfo2(info));
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onPlaybackInfoChanged(mInstance, MediaUtils.toPlaybackInfo2(info));
}
});
}
@@ -1339,13 +1316,12 @@
return;
}
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
+ public void run(@NonNull ControllerCallback callback) {
Bundle args = new Bundle();
args.putBoolean(MediaConstants.ARGUMENT_CAPTIONING_ENABLED, enabled);
- mCallback.onCustomCommand(mInstance, new SessionCommand(
+ callback.onCustomCommand(mInstance, new SessionCommand(
SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED, null), args);
}
});
@@ -1359,11 +1335,10 @@
}
mRepeatMode = repeatMode;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onRepeatModeChanged(mInstance, repeatMode);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onRepeatModeChanged(mInstance, repeatMode);
}
});
}
@@ -1376,11 +1351,10 @@
}
mShuffleMode = shuffleMode;
}
- if (mCallback == null) return;
- mCallbackExecutor.execute(new Runnable() {
+ mInstance.notifyControllerCallback(new ControllerCallbackRunnable() {
@Override
- public void run() {
- mCallback.onShuffleModeChanged(mInstance, shuffleMode);
+ public void run(@NonNull ControllerCallback callback) {
+ callback.onShuffleModeChanged(mInstance, shuffleMode);
}
});
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java
index f9bab74..73271b7 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerStub.java
@@ -305,7 +305,7 @@
}
return;
}
- controller.getInstance().close();
+ controller.mInstance.close();
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaInterface.java b/media2/session/src/main/java/androidx/media2/session/MediaInterface.java
index 567f6d4c..24898b3 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaInterface.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaInterface.java
@@ -16,6 +16,8 @@
package androidx.media2.session;
+import android.view.Surface;
+
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.SessionPlayer.PlayerResult;
@@ -76,5 +78,6 @@
// Common interface for session and controller
interface SessionPlayer extends SessionPlaybackControl, SessionPlaylistControl {
VideoSize getVideoSize();
+ ListenableFuture<PlayerResult> setSurface(Surface surface);
}
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
index 0aea8b8..e7af683 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
@@ -54,6 +54,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -850,6 +851,16 @@
}, new VideoSize(0, 0));
}
+ @Override
+ public ListenableFuture<PlayerResult> setSurface(final Surface surface) {
+ return dispatchPlayerTask(new PlayerTask<ListenableFuture<PlayerResult>>() {
+ @Override
+ public ListenableFuture<PlayerResult> run(@NonNull SessionPlayer player) {
+ return player.setSurfaceInternal(surface);
+ }
+ });
+ }
+
///////////////////////////////////////////////////
// package private and private methods
///////////////////////////////////////////////////
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
index 2f59751..ebcfbd3 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
@@ -19,6 +19,7 @@
import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
import static androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE;
import static androidx.media2.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
+import static androidx.media2.session.SessionCommand.COMMAND_VERSION_CURRENT;
import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
import android.content.Context;
@@ -67,8 +68,8 @@
static {
SessionCommandGroup group = new SessionCommandGroup.Builder()
- .addAllPlayerCommands(SessionCommand.COMMAND_VERSION_CURRENT)
- .addAllVolumeCommands(SessionCommand.COMMAND_VERSION_CURRENT)
+ .addAllPlayerCommands(COMMAND_VERSION_CURRENT, /* includeHidden= */ false)
+ .addAllVolumeCommands(COMMAND_VERSION_CURRENT)
.build();
Set<SessionCommand> commands = group.getCommands();
for (SessionCommand command : commands) {
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java b/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java
index ac0fd5f..f45d484 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaSessionStub.java
@@ -33,6 +33,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
+import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -79,7 +80,7 @@
static {
SessionCommandGroup group = new SessionCommandGroup.Builder()
- .addAllPlayerCommands(COMMAND_VERSION_CURRENT)
+ .addAllPlayerCommands(COMMAND_VERSION_CURRENT, /* includeHidden= */ false)
.addAllVolumeCommands(COMMAND_VERSION_CURRENT)
.build();
Set<SessionCommand> commands = group.getCommands();
@@ -1018,6 +1019,20 @@
});
}
+ @Override
+ public void setSurface(IMediaController caller, int seq, final Surface surface) {
+ if (caller == null) {
+ return;
+ }
+ dispatchSessionTask(caller, seq, SessionCommand.COMMAND_CODE_PLAYER_SET_SURFACE,
+ new SessionPlayerTask() {
+ @Override
+ public ListenableFuture<PlayerResult> run(ControllerInfo controller) {
+ return mSessionImpl.setSurface(surface);
+ }
+ });
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// AIDL methods for LibrarySession overrides
//////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaUtils.java b/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
index ec6ce2d..c1210ab 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
@@ -804,11 +804,10 @@
public static SessionCommandGroup convertToSessionCommandGroup(long sessionFlags,
PlaybackStateCompat state) {
SessionCommandGroup.Builder commandsBuilder = new SessionCommandGroup.Builder();
+ commandsBuilder.addAllPlayerBasicCommands(COMMAND_VERSION_CURRENT);
boolean includePlaylistCommands = (sessionFlags & FLAG_HANDLES_QUEUE_COMMANDS) != 0;
if (includePlaylistCommands) {
- commandsBuilder.addAllPlayerCommands(COMMAND_VERSION_CURRENT);
- } else {
- commandsBuilder.addAllPlayerBasicCommands(COMMAND_VERSION_CURRENT);
+ commandsBuilder.addAllPlayerPlaylistCommands(COMMAND_VERSION_CURRENT);
}
commandsBuilder.addAllVolumeCommands(COMMAND_VERSION_CURRENT);
commandsBuilder.addAllSessionCommands(COMMAND_VERSION_CURRENT);
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionCommand.java b/media2/session/src/main/java/androidx/media2/session/SessionCommand.java
index 0a892b0..acbfeba 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionCommand.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionCommand.java
@@ -16,11 +16,13 @@
package androidx.media2.session;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
+import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -99,6 +101,7 @@
COMMAND_CODE_PLAYER_GET_CURRENT_MEDIA_ITEM,
COMMAND_CODE_PLAYER_UPDATE_LIST_METADATA,
COMMAND_CODE_PLAYER_SET_MEDIA_ITEM,
+ COMMAND_CODE_PLAYER_SET_SURFACE,
COMMAND_CODE_VOLUME_SET_VOLUME,
COMMAND_CODE_VOLUME_ADJUST_VOLUME,
COMMAND_CODE_SESSION_FAST_FORWARD,
@@ -134,6 +137,7 @@
////////////////////////////////////////////////////////////////////////////////////////////////
static final ArrayMap<Integer, Range> VERSION_PLAYER_BASIC_COMMANDS_MAP = new ArrayMap<>();
static final ArrayMap<Integer, Range> VERSION_PLAYER_PLAYLIST_COMMANDS_MAP = new ArrayMap<>();
+ static final ArrayMap<Integer, Range> VERSION_PLAYER_HIDDEN_COMMANDS_MAP = new ArrayMap<>();
/**
* Command code for {@link MediaController#play()}.
@@ -336,6 +340,20 @@
*/
public static final int COMMAND_CODE_PLAYER_SET_MEDIA_ITEM = 10018;
+ /**
+ * Command code for {@link MediaController#setSurface(Surface)}.
+ * <p>
+ * Command would be sent directly to the player if the session doesn't reject the request
+ * through the
+ * {@link SessionCallback#onCommandRequest(MediaSession, ControllerInfo, SessionCommand)}.
+ * <p>
+ * Code version is {@link #COMMAND_VERSION_1}.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public static final int COMMAND_CODE_PLAYER_SET_SURFACE = 11000;
+
static {
VERSION_PLAYER_BASIC_COMMANDS_MAP.put(COMMAND_VERSION_1,
new Range(COMMAND_CODE_PLAYER_PLAY, COMMAND_CODE_PLAYER_SET_SPEED));
@@ -347,6 +365,11 @@
COMMAND_CODE_PLAYER_SET_MEDIA_ITEM));
}
+ static {
+ VERSION_PLAYER_HIDDEN_COMMANDS_MAP.put(COMMAND_VERSION_1,
+ new Range(COMMAND_CODE_PLAYER_SET_SURFACE, COMMAND_CODE_PLAYER_SET_SURFACE));
+ }
+
////////////////////////////////////////////////////////////////////////////////////////////////
// Volume commands (i.e. commands to {@link AudioManager} or {@link RouteMediaPlayer})
////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionCommandGroup.java b/media2/session/src/main/java/androidx/media2/session/SessionCommandGroup.java
index 90cdf8d..ddc20f7 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionCommandGroup.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionCommandGroup.java
@@ -148,7 +148,7 @@
if (version != COMMAND_VERSION_1) {
throw new IllegalArgumentException("Unknown command version " + version);
}
- addAllPlayerCommands(version);
+ addAllPlayerCommands(version, /* includeHidden= */ true);
addAllVolumeCommands(version);
addAllSessionCommands(version);
addAllLibraryCommands(version);
@@ -168,9 +168,10 @@
return this;
}
- @NonNull Builder addAllPlayerCommands(@CommandVersion int version) {
+ @NonNull Builder addAllPlayerCommands(@CommandVersion int version, boolean includeHidden) {
addAllPlayerBasicCommands(version);
addAllPlayerPlaylistCommands(version);
+ if (includeHidden) addAllPlayerHiddenCommands(version);
return this;
}
@@ -184,6 +185,11 @@
return this;
}
+ @NonNull Builder addAllPlayerHiddenCommands(@CommandVersion int version) {
+ addCommands(version, SessionCommand.VERSION_PLAYER_HIDDEN_COMMANDS_MAP);
+ return this;
+ }
+
@NonNull Builder addAllVolumeCommands(@CommandVersion int version) {
addCommands(version, SessionCommand.VERSION_VOLUME_COMMANDS_MAP);
return this;
diff --git a/media2/session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl b/media2/session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl
index 4f57d6b..efd7850 100644
--- a/media2/session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl
+++ b/media2/session/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaSession.aidl
@@ -49,6 +49,7 @@
void notifyCurrentMediaItemChanged(String sessionId, int index);
void notifyAudioAttributesChanged(String sessionId, in ParcelImpl attrs);
void notifyVideoSizeChanged(String sessionId, in ParcelImpl videoSize);
+ boolean surfaceExists(String sessionId);
void setPlaylist(String sessionId, in List<ParcelImpl> playlist);
void createAndSetDummyPlaylist(String sessionId, int size);
diff --git a/media2/session/version-compat-tests/current/client/build.gradle b/media2/session/version-compat-tests/current/client/build.gradle
index 97ac2c2..d2be13b 100644
--- a/media2/session/version-compat-tests/current/client/build.gradle
+++ b/media2/session/version-compat-tests/current/client/build.gradle
@@ -28,6 +28,7 @@
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
}
android {
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml b/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
index 211dcff..ca16534 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.media2.test.client.test">
<application android:supportsRtl="true">
+ <activity android:name="androidx.media2.test.client.SurfaceActivity" />
+
<service android:name="androidx.media2.test.client.MediaControllerProviderService">
<intent-filter>
<!-- Keep sync with CommonConstants.java -->
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
index c248273..67cd431 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
@@ -505,6 +505,10 @@
Log.e(TAG, "Failed to call notifyVideoSizeChanged()");
}
}
+
+ public boolean surfaceExists() throws RemoteException {
+ return mBinder.surfaceExists(mSessionId);
+ }
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java
new file mode 100644
index 0000000..d2e6d81
--- /dev/null
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/SurfaceActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 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 androidx.media2.test.client;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import androidx.media2.test.client.test.R;
+
+public class SurfaceActivity extends Activity {
+ private SurfaceHolder mHolder;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_surface);
+
+ SurfaceView surfaceView = findViewById(R.id.surface_view);
+ mHolder = surfaceView.getHolder();
+ }
+
+ public SurfaceHolder getSurfaceHolder() {
+ return mHolder;
+ }
+}
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java
new file mode 100644
index 0000000..401d4b6
--- /dev/null
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaController_SurfaceTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 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 androidx.media2.test.client.tests;
+
+import static android.content.Context.KEYGUARD_SERVICE;
+
+import static androidx.media2.test.common.CommonConstants.DEFAULT_TEST_NAME;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.app.KeyguardManager;
+import android.os.Build;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import androidx.media2.session.MediaController;
+import androidx.media2.session.SessionResult;
+import androidx.media2.test.client.RemoteMediaSession;
+import androidx.media2.test.client.SurfaceActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaController#setSurface(Surface)}.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class MediaController_SurfaceTest extends MediaSessionTestBase {
+ private static final String TAG = "MC_SurfaceTest";
+
+ private Instrumentation mInstrumentation;
+ private SurfaceActivity mActivity;
+ private RemoteMediaSession mRemoteSession;
+
+ @Rule
+ public ActivityTestRule<SurfaceActivity> mActivityRule =
+ new ActivityTestRule<>(SurfaceActivity.class);
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+
+ setKeepScreenOn();
+
+ mRemoteSession = new RemoteMediaSession(DEFAULT_TEST_NAME, mContext, null);
+ }
+
+ @After
+ @Override
+ public void cleanUp() throws Exception {
+ super.cleanUp();
+
+ mRemoteSession.cleanUp();
+ }
+
+ @Test
+ public void testSetSurface() throws Exception {
+ prepareLooper();
+ MediaController controller = createController(mRemoteSession.getToken());
+
+ // Set
+ final Surface testSurface = mActivity.getSurfaceHolder().getSurface();
+ SessionResult result = controller.setSurface(testSurface)
+ .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
+ assertTrue(mRemoteSession.getMockPlayer().surfaceExists());
+
+ // Reset
+ result = controller.setSurface(null).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals(SessionResult.RESULT_SUCCESS, result.getResultCode());
+ assertFalse(mRemoteSession.getMockPlayer().surfaceExists());
+ }
+
+ private void setKeepScreenOn() throws Exception {
+ try {
+ setKeepScreenOnOrThrow();
+ } catch (Throwable tr) {
+ throw new Exception(tr);
+ }
+ }
+
+ private void setKeepScreenOnOrThrow() throws Throwable {
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (Build.VERSION.SDK_INT >= 27) {
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mActivity.setTurnScreenOn(true);
+ mActivity.setShowWhenLocked(true);
+ KeyguardManager keyguardManager = (KeyguardManager)
+ mInstrumentation.getTargetContext().getSystemService(KEYGUARD_SERVICE);
+ keyguardManager.requestDismissKeyguard(mActivity, null);
+ } else {
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+}
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/res/layout/activity_surface.xml b/media2/session/version-compat-tests/current/client/src/androidTest/res/layout/activity_surface.xml
new file mode 100644
index 0000000..7945f97
--- /dev/null
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/res/layout/activity_surface.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2019 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:keepScreenOn="true">
+ <SurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ </SurfaceView>
+</LinearLayout>
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
index c797948..cefed2d 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
@@ -527,5 +527,12 @@
VideoSize videoSizeObj = MediaParcelUtils.fromParcelable(videoSize);
player.notifyVideoSizeChanged(videoSizeObj);
}
+
+ @Override
+ public boolean surfaceExists(String sessionId) {
+ MediaSession session = mSessionMap.get(sessionId);
+ MockPlayer player = (MockPlayer) session.getPlayer();
+ return player.surfaceExists();
+ }
}
}
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
index a1087ae..d259c0a 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MockPlayer.java
@@ -16,7 +16,10 @@
package androidx.media2.test.service;
+import android.view.Surface;
+
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.media.AudioAttributesCompat;
import androidx.media2.common.MediaItem;
@@ -61,6 +64,7 @@
public @RepeatMode int mRepeatMode = -1;
public @ShuffleMode int mShuffleMode = -1;
public VideoSize mVideoSize = new VideoSize(0, 0);
+ public Surface mSurface;
public boolean mSetPlaylistCalled;
public boolean mUpdatePlaylistMetadataCalled;
@@ -523,4 +527,15 @@
});
}
}
+
+ @Override
+ @NonNull
+ public ListenableFuture<PlayerResult> setSurfaceInternal(@Nullable Surface surface) {
+ mSurface = surface;
+ return new SyncListenableFuture(mCurrentMediaItem);
+ }
+
+ public boolean surfaceExists() {
+ return mSurface != null;
+ }
}
diff --git a/media2/widget/build.gradle b/media2/widget/build.gradle
index 31eb89f..7b6af85 100644
--- a/media2/widget/build.gradle
+++ b/media2/widget/build.gradle
@@ -27,7 +27,6 @@
api(project(":media2:media2-common"))
api(project(":media2:media2-player"))
api(project(":media2:media2-session"))
- implementation(project(":mediarouter"))
implementation("androidx.appcompat:appcompat:1.0.2")
implementation("androidx.palette:palette:1.0.0")
implementation(project(":concurrent:concurrent-futures"))
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
index 88a83cd..647c1eb 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
@@ -280,7 +280,7 @@
}
@Test
- public void testGetMetadataFromMusic() throws Throwable {
+ public void testGetMetadataFromMusicFile() throws Throwable {
Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
+ R.raw.test_music);
AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.test_music);
@@ -296,6 +296,11 @@
.setFileDescriptorLength(afd.getLength())
.build();
afd.close();
+ // onCurrentMediaItemChanged is expected to be called 3 times:
+ // 1) after VideoView#setMediaItem is called.
+ // 2) after MediaSessionImplBase updates duration metadata by calling
+ // MediaItem#setMetadata.
+ // 3) after VideoView extracts metadata and calls MediaItem#setMetadata.
final CountDownLatch latchForUri = new CountDownLatch(3);
final CountDownLatch latchForFile = new CountDownLatch(3);
final MediaController controller =
@@ -349,6 +354,36 @@
}
@Test
+ public void testButtonVisibilityForMusicFile() throws Throwable {
+ Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
+ + R.raw.test_music);
+ final MediaItem uriMediaItem = createTestMediaItem2(uri);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final MediaController controller =
+ createController(new MediaController.ControllerCallback() {
+ @NonNull
+ @Override
+ public SessionResult onCustomCommand(@NonNull MediaController controller,
+ @NonNull SessionCommand command, @Nullable Bundle args) {
+ if (command.getCustomAction()
+ == MediaControlView.EVENT_UPDATE_TRACK_STATUS) {
+ latch.countDown();
+ }
+ return new SessionResult(SessionResult.RESULT_SUCCESS, null);
+ }
+ });
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mVideoView.setMediaItem(uriMediaItem);
+ }
+ });
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ onView(withId(R.id.subtitle)).check(matches(not(isDisplayed())));
+ }
+
+ @Test
public void testUpdateAndSelectSubtitleTrack() throws Throwable {
Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
+ R.raw.testvideo_with_2_subtitle_tracks);
@@ -446,7 +481,7 @@
});
}
});
- onView(withId(R.id.fullscreen)).check(matches(isDisplayed()));
+ onView(withId(R.id.fullscreen)).check(matches(isCompletelyDisplayed()));
onView(withId(R.id.fullscreen)).perform(click());
assertTrue(latchOn.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
diff --git a/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java b/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java
index 1a30d1e..7f8b189 100644
--- a/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java
+++ b/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java
@@ -66,8 +66,6 @@
import androidx.media2.session.SessionCommandGroup;
import androidx.media2.session.SessionResult;
import androidx.media2.session.SessionToken;
-import androidx.mediarouter.app.MediaRouteButton;
-import androidx.mediarouter.media.MediaRouteSelector;
import java.util.ArrayList;
import java.util.Arrays;
@@ -201,7 +199,6 @@
private View mTitleBar;
private TextView mTitleView;
private View mAdExternalLink;
- private MediaRouteButton mRouteButton;
// Relating to Center View
ViewGroup mCenterView;
@@ -526,16 +523,6 @@
}
}
- void setRouteSelector(MediaRouteSelector selector) {
- if (selector != null && !selector.isEmpty()) {
- mRouteButton.setRouteSelector(selector);
- mRouteButton.setVisibility(View.VISIBLE);
- } else {
- mRouteButton.setRouteSelector(MediaRouteSelector.EMPTY);
- mRouteButton.setVisibility(View.GONE);
- }
- }
-
void setDelayedAnimationInterval(long interval) {
mDelayedAnimationIntervalMs = interval;
}
@@ -555,7 +542,6 @@
mTitleBar = findViewById(R.id.title_bar);
mTitleView = findViewById(R.id.title_text);
mAdExternalLink = findViewById(R.id.ad_external_link);
- mRouteButton = findViewById(R.id.cast);
// Relating to Center View
mCenterView = findViewById(R.id.center_view);
diff --git a/media2/widget/src/main/java/androidx/media2/widget/RoutePlayer.java b/media2/widget/src/main/java/androidx/media2/widget/RoutePlayer.java
deleted file mode 100644
index b51bd3d..0000000
--- a/media2/widget/src/main/java/androidx/media2/widget/RoutePlayer.java
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media2.widget;
-
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_BAD_VALUE;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_INVALID_STATE;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_ERROR_UNKNOWN;
-import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.core.util.Pair;
-import androidx.media.AudioAttributesCompat;
-import androidx.media2.common.MediaItem;
-import androidx.media2.common.MediaMetadata;
-import androidx.media2.common.SessionPlayer;
-import androidx.media2.common.UriMediaItem;
-import androidx.media2.session.RemoteSessionPlayer;
-import androidx.mediarouter.media.MediaItemStatus;
-import androidx.mediarouter.media.MediaRouteSelector;
-import androidx.mediarouter.media.MediaRouter;
-import androidx.mediarouter.media.MediaSessionStatus;
-import androidx.mediarouter.media.RemotePlaybackClient;
-import androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback;
-import androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback;
-import androidx.mediarouter.media.RemotePlaybackClient.StatusCallback;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-
-/* package */ class RoutePlayer extends RemoteSessionPlayer {
- private static final String TAG = "RoutePlayer";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int ITEM_NONE = -1;
-
- String mItemId;
- int mCurrentPlayerState;
- long mDuration;
- long mLastStatusChangedTime;
- long mPosition;
- boolean mCanResume;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- MediaRouter.RouteInfo mSelectedRoute;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final List<ResolvableFuture<PlayerResult>> mPendingVolumeResult = new ArrayList<>();
-
- private MediaItem mItem;
- private MediaRouter mMediaRouter;
- private RemotePlaybackClient mClient;
-
- private MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() {
- @Override
- public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
- if (TextUtils.equals(route.getId(), mSelectedRoute.getId())) {
- final int volume = route.getVolume();
- for (int i = 0; i < mPendingVolumeResult.size(); i++) {
- mPendingVolumeResult.get(i).set(new PlayerResult(
- RESULT_SUCCESS, getCurrentMediaItem()));
- }
- mPendingVolumeResult.clear();
- List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
- for (Pair<PlayerCallback, Executor> pair : callbacks) {
- if (pair.first instanceof RemoteSessionPlayer.Callback) {
- final RemoteSessionPlayer.PlayerCallback callback = pair.first;
- pair.second.execute(new Runnable() {
- @Override
- public void run() {
- ((RemoteSessionPlayer.Callback) callback)
- .onVolumeChanged(RoutePlayer.this, volume);
- }
- });
- }
- }
- }
- }
- };
-
- private StatusCallback mStatusCallback = new StatusCallback() {
- @Override
- public void onItemStatusChanged(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus,
- String itemId, MediaItemStatus itemStatus) {
- if (DEBUG && !isSessionActive(sessionStatus)) {
- Log.v(TAG, "onItemStatusChanged() is called, but session is not active.");
- }
- mLastStatusChangedTime = SystemClock.elapsedRealtime();
- mPosition = itemStatus.getContentPosition();
- mCurrentPlayerState = convertPlaybackStateToPlayerState(itemStatus.getPlaybackState());
-
- List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
- for (Pair<PlayerCallback, Executor> pair : callbacks) {
- final PlayerCallback callback = pair.first;
- pair.second.execute(new Runnable() {
- @Override
- public void run() {
- callback.onPlayerStateChanged(RoutePlayer.this, mCurrentPlayerState);
- }
- });
- }
- }
- };
-
- public RoutePlayer(Context context, MediaRouteSelector selector,
- MediaRouter.RouteInfo route) {
- mMediaRouter = MediaRouter.getInstance(context);
- mMediaRouter.addCallback(selector, mRouterCallback);
- mSelectedRoute = route;
-
- mClient = new RemotePlaybackClient(context, route);
- mClient.setStatusCallback(mStatusCallback);
- if (mClient.isSessionManagementSupported()) {
- mClient.startSession(null, new SessionActionCallback() {
- @Override
- public void onResult(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus) {
- if (DEBUG && !isSessionActive(sessionStatus)) {
- Log.v(TAG, "RoutePlayer has been initialized, but session is not"
- + "active.");
- }
- }
- });
- }
- }
-
- @Override
- public ListenableFuture<PlayerResult> play() {
- if (mItem == null) {
- return createResult(RESULT_ERROR_BAD_VALUE);
- }
-
- // RemotePlaybackClient cannot call resume(..) without calling pause(..) first.
- if (!mCanResume) {
- return playInternal();
- }
-
- if (mClient.isSessionManagementSupported()) {
- final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- mClient.resume(null, new SessionActionCallback() {
- @Override
- public void onResult(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus) {
- if (DEBUG && !isSessionActive(sessionStatus)) {
- Log.v(TAG, "play() is called, but session is not active.");
- }
- // Do nothing since this returns the buffering state--
- // StatusCallback#onItemStatusChanged is called when the session reaches the
- // play state.
- result.set(new PlayerResult(RESULT_SUCCESS, getCurrentMediaItem()));
- }
- });
- }
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> prepare() {
- return createResult();
- }
-
- @Override
- public ListenableFuture<PlayerResult> pause() {
- if (mClient.isSessionManagementSupported()) {
- final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- mClient.pause(null, new SessionActionCallback() {
- @Override
- public void onResult(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus) {
- if (DEBUG && !isSessionActive(sessionStatus)) {
- Log.v(TAG, "pause() is called, but session is not active.");
- }
- mCanResume = true;
- // Do not update playback state here since this returns the buffering state--
- // StatusCallback#onItemStatusChanged is called when the session reaches the
- // pause state.
- result.set(new PlayerResult(RESULT_SUCCESS, getCurrentMediaItem()));
- }
- });
- }
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> seekTo(long pos) {
- if (mClient.isSessionManagementSupported()) {
- final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- mClient.seek(mItemId, pos, null, new ItemActionCallback() {
- @Override
- public void onResult(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus,
- String itemId, final MediaItemStatus itemStatus) {
- if (DEBUG && !isSessionActive(sessionStatus)) {
- Log.v(TAG, "seekTo(long) is called, but session is not active.");
- }
- if (itemStatus != null) {
- List<Pair<PlayerCallback, Executor>> callbacks = getCallbacks();
- for (Pair<PlayerCallback, Executor> pair : callbacks) {
- final PlayerCallback callback = pair.first;
- pair.second.execute(new Runnable() {
- @Override
- public void run() {
- callback.onSeekCompleted(RoutePlayer.this,
- itemStatus.getContentPosition());
- }
- });
- }
- } else {
- result.set(new PlayerResult(RESULT_ERROR_UNKNOWN,
- getCurrentMediaItem()));
- }
- }
- });
- }
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public long getCurrentPosition() {
- long expectedPosition = mPosition;
- if (mCurrentPlayerState == PLAYER_STATE_PLAYING) {
- expectedPosition = mPosition + (SystemClock.elapsedRealtime() - mLastStatusChangedTime);
- }
- return expectedPosition;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- @Override
- public long getBufferedPosition() {
- return 0;
- }
-
- @Override
- public int getPlayerState() {
- return mCurrentPlayerState;
- }
-
- @Override
- public int getBufferingState() {
- return SessionPlayer.BUFFERING_STATE_UNKNOWN;
- }
-
- @Override
- public ListenableFuture<PlayerResult> setAudioAttributes(AudioAttributesCompat attributes) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public AudioAttributesCompat getAudioAttributes() {
- return null;
- }
-
- @Override
- public ListenableFuture<PlayerResult> setMediaItem(MediaItem item) {
- mItem = item;
- return createResult();
- }
-
- @Override
- public MediaItem getCurrentMediaItem() {
- return mItem;
- }
-
- @Override
- public int getCurrentMediaItemIndex() {
- return ITEM_NONE;
- }
-
- @Override
- public int getPreviousMediaItemIndex() {
- return ITEM_NONE;
- }
-
- @Override
- public int getNextMediaItemIndex() {
- return ITEM_NONE;
- }
-
- @Override
- public ListenableFuture<PlayerResult> setPlaybackSpeed(float speed) {
- // Do nothing
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public float getPlaybackSpeed() {
- return 1.0f;
- }
-
- @Override
- public int getVolume() {
- return mSelectedRoute.getVolume();
- }
-
- @Override
- public Future<PlayerResult> adjustVolume(int direction) {
- mSelectedRoute.requestUpdateVolume(direction);
-
- ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- mPendingVolumeResult.add(result);
- return result;
- }
-
- @Override
- public Future<PlayerResult> setVolume(int volume) {
- mSelectedRoute.requestSetVolume(volume);
-
- ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- mPendingVolumeResult.add(result);
- return result;
- }
-
- @Override
- public int getMaxVolume() {
- return mSelectedRoute.getVolumeMax();
- }
-
- @Override
- public int getVolumeControlType() {
- return mSelectedRoute.getVolumeHandling();
- }
-
- @Override
- public ListenableFuture<PlayerResult> setPlaylist(List<MediaItem> list,
- MediaMetadata metadata) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem item) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> removePlaylistItem(int index) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem item) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> skipToPreviousPlaylistItem() {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> skipToNextPlaylistItem() {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> skipToPlaylistItem(int index) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> updatePlaylistMetadata(MediaMetadata metadata) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> setRepeatMode(int repeatMode) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public ListenableFuture<PlayerResult> setShuffleMode(int shuffleMode) {
- // TODO: implement
- return createResult(RESULT_ERROR_INVALID_STATE);
- }
-
- @Override
- public List<MediaItem> getPlaylist() {
- List<MediaItem> list = new ArrayList<>();
- list.add(mItem);
- return list;
- }
-
- @Override
- public MediaMetadata getPlaylistMetadata() {
- return null;
- }
-
- @Override
- public int getRepeatMode() {
- return SessionPlayer.REPEAT_MODE_NONE;
- }
-
- @Override
- public int getShuffleMode() {
- return SessionPlayer.SHUFFLE_MODE_NONE;
- }
-
- @Override
- public void close() {
- if (mClient != null) {
- try {
- mClient.release();
- } catch (IllegalArgumentException e) {
- Log.d(TAG, "Receiver not registered");
- }
- mClient = null;
- }
- mMediaRouter.removeCallback(mRouterCallback);
- }
-
- void setCurrentPosition(long position) {
- mPosition = position;
- }
-
- boolean isSessionActive(MediaSessionStatus status) {
- if (status == null || status.getSessionState() == MediaSessionStatus.SESSION_STATE_ENDED
- || status.getSessionState() == MediaSessionStatus.SESSION_STATE_INVALIDATED) {
- return false;
- }
- return true;
- }
-
- int convertPlaybackStateToPlayerState(int playbackState) {
- int playerState = PLAYER_STATE_IDLE;
- switch (playbackState) {
- case MediaItemStatus.PLAYBACK_STATE_PENDING:
- case MediaItemStatus.PLAYBACK_STATE_FINISHED:
- case MediaItemStatus.PLAYBACK_STATE_CANCELED:
- playerState = PLAYER_STATE_IDLE;
- break;
- case MediaItemStatus.PLAYBACK_STATE_PLAYING:
- playerState = PLAYER_STATE_PLAYING;
- break;
- case MediaItemStatus.PLAYBACK_STATE_PAUSED:
- case MediaItemStatus.PLAYBACK_STATE_BUFFERING:
- playerState = PLAYER_STATE_PAUSED;
- break;
- case MediaItemStatus.PLAYBACK_STATE_INVALIDATED:
- case MediaItemStatus.PLAYBACK_STATE_ERROR:
- playerState = PLAYER_STATE_ERROR;
- break;
- }
- return playerState;
- }
-
- private ListenableFuture<PlayerResult> playInternal() {
- if (!(mItem instanceof UriMediaItem)) {
- Log.w(TAG, "Data source type is not Uri." + mItem);
- return createResult(RESULT_ERROR_BAD_VALUE);
- }
- final ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- mClient.play(((UriMediaItem) mItem).getUri(), "video/mp4", null, mPosition, null,
- new ItemActionCallback() {
- @Override
- public void onResult(Bundle data, String sessionId,
- MediaSessionStatus sessionStatus,
- String itemId, MediaItemStatus itemStatus) {
- if (DEBUG && !isSessionActive(sessionStatus)) {
- Log.v(TAG, "play() is called, but session is not active.");
- }
- mItemId = itemId;
- if (itemStatus != null) {
- mDuration = itemStatus.getContentDuration();
- }
- // Do not update playback state here since this returns the buffering state.
- // StatusCallback#onItemStatusChanged is called when the session reaches the
- // play state.
- result.set(new PlayerResult(RESULT_SUCCESS, getCurrentMediaItem()));
- }
- });
- return result;
- }
-
- private ListenableFuture<PlayerResult> createResult() {
- return createResult(RESULT_SUCCESS);
- }
-
- private ListenableFuture<PlayerResult> createResult(int code) {
- ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
- result.set(new PlayerResult(code, getCurrentMediaItem()));
- return result;
- }
-}
diff --git a/media2/widget/src/main/java/androidx/media2/widget/VideoView.java b/media2/widget/src/main/java/androidx/media2/widget/VideoView.java
index 2c38673..bceb17b 100644
--- a/media2/widget/src/main/java/androidx/media2/widget/VideoView.java
+++ b/media2/widget/src/main/java/androidx/media2/widget/VideoView.java
@@ -57,14 +57,10 @@
import androidx.media2.player.subtitle.SubtitleTrack;
import androidx.media2.session.MediaController;
import androidx.media2.session.MediaSession;
-import androidx.media2.session.RemoteSessionPlayer;
import androidx.media2.session.SessionCommand;
import androidx.media2.session.SessionCommandGroup;
import androidx.media2.session.SessionResult;
import androidx.media2.session.SessionToken;
-import androidx.mediarouter.media.MediaControlIntent;
-import androidx.mediarouter.media.MediaRouteSelector;
-import androidx.mediarouter.media.MediaRouter;
import androidx.palette.graphics.Palette;
import java.lang.annotation.Retention;
@@ -197,58 +193,6 @@
SubtitleAnchorView mSubtitleAnchorView;
- private MediaRouter mMediaRouter;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- MediaRouteSelector mRouteSelector;
- MediaRouter.RouteInfo mRoute;
- RoutePlayer mRoutePlayer;
-
- private final MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() {
- @Override
- public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
- if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
- // Save local playback state and position
- int localPlaybackState = mCurrentState;
- long localPlaybackPosition = (mMediaSession == null)
- ? 0 : mMediaSession.getPlayer().getCurrentPosition();
-
- // Update player
- resetPlayer();
- mRoute = route;
- mRoutePlayer = new RoutePlayer(getContext(), mRouteSelector, route);
- // TODO: Replace with MediaSession#setPlaylist once b/110811730 is fixed.
- mRoutePlayer.setMediaItem(mMediaItem);
- mRoutePlayer.setCurrentPosition(localPlaybackPosition);
- ensureSessionWithPlayer(mRoutePlayer);
- if (localPlaybackState == STATE_PLAYING) {
- mMediaSession.getPlayer().play();
- }
- }
- }
-
- @Override
- public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route, int reason) {
- long currentPosition = 0;
- int currentState = 0;
- if (mRoute != null && mRoutePlayer != null) {
- currentPosition = mRoutePlayer.getCurrentPosition();
- currentState = mRoutePlayer.getPlayerState();
- mRoutePlayer.close();
- mRoutePlayer = null;
- }
- if (mRoute == route) {
- mRoute = null;
- }
- if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
- openVideo();
- mMediaSession.getPlayer().seekTo(currentPosition);
- if (currentState == SessionPlayer.PLAYER_STATE_PLAYING) {
- mMediaSession.getPlayer().play();
- }
- }
- }
- };
-
private final VideoViewInterface.SurfaceListener mSurfaceListener =
new VideoViewInterface.SurfaceListener() {
@Override
@@ -381,12 +325,6 @@
mCurrentView = mTextureView;
}
mTargetView = mCurrentView;
-
- MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
- builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
- builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
- builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
- mRouteSelector = builder.build();
}
/**
@@ -545,11 +483,6 @@
ensureSessionWithPlayer(mMediaPlayer);
attachMediaControlView();
- mMediaRouter = MediaRouter.getInstance(getContext());
- // TODO: Revisit once ag/4207152 is merged.
- mMediaRouter.setMediaSessionCompat(mMediaSession.getSessionCompat());
- mMediaRouter.addCallback(mRouteSelector, mRouterCallback,
- MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
@Override
@@ -652,7 +585,7 @@
boolean needToStart() {
return mMediaSession != null
- && (mMediaPlayer != null || mRoutePlayer != null) && isWaitingPlayback();
+ && mMediaPlayer != null && isWaitingPlayback();
}
private boolean isWaitingPlayback() {
@@ -666,10 +599,6 @@
}
if (mMediaItem != null) {
resetPlayer();
- if (isRemotePlayback()) {
- mRoutePlayer.setMediaItem(mMediaItem);
- return;
- }
}
try {
@@ -752,12 +681,6 @@
}
}
- boolean isRemotePlayback() {
- return mRoutePlayer != null
- && mMediaSession != null
- && (mMediaSession.getPlayer() instanceof RemoteSessionPlayer);
- }
-
void selectSubtitleTrack(TrackInfo trackInfo) {
if (!isMediaPrepared()) {
return;
@@ -1017,16 +940,6 @@
task.execute();
}
- if (mMediaControlView != null) {
- Uri uri = (mMediaItem instanceof UriMediaItem)
- ? ((UriMediaItem) mMediaItem).getUri() : null;
- if (uri != null && UriUtil.isFromNetwork(uri)) {
- mMediaControlView.setRouteSelector(mRouteSelector);
- } else {
- mMediaControlView.setRouteSelector(null);
- }
- }
-
if (player instanceof MediaPlayer) {
if (needToStart()) {
mMediaSession.getPlayer().play();
@@ -1096,10 +1009,6 @@
Log.w(TAG, "onCustomCommand() is ignored. session is already gone.");
}
}
- if (isRemotePlayback()) {
- // TODO: call mRoutePlayer.onCommand()
- return new SessionResult(RESULT_SUCCESS, null);
- }
switch (command.getCustomAction()) {
case MediaControlView.COMMAND_SHOW_SUBTITLE:
int indexInSubtitleTrackList = args != null ? args.getInt(
diff --git a/media2/widget/src/main/res/layout/media_controller.xml b/media2/widget/src/main/res/layout/media_controller.xml
index bc49531..7228241 100644
--- a/media2/widget/src/main/res/layout/media_controller.xml
+++ b/media2/widget/src/main/res/layout/media_controller.xml
@@ -101,11 +101,6 @@
android:layout_height="wrap_content"
style="@style/TitleBarButton.Launch" />
</LinearLayout>
-
- <view class="androidx.mediarouter.app.MediaRouteButton"
- android:id="@+id/cast"
- android:visibility="gone"
- style="@style/TitleBarButton" />
</LinearLayout>
</LinearLayout>
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
new file mode 100644
index 0000000..25b3700
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019 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 androidx.navigation.fragment
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentFactory
+import androidx.fragment.app.FragmentManager
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class DialogFragmentNavigatorTest {
+
+ companion object {
+ private const val INITIAL_FRAGMENT = 1
+ }
+
+ @get:Rule
+ var activityRule = ActivityTestRule(EmptyActivity::class.java)
+
+ private lateinit var emptyActivity: EmptyActivity
+ private lateinit var fragmentManager: FragmentManager
+
+ @Before
+ fun setup() {
+ emptyActivity = activityRule.activity
+ fragmentManager = emptyActivity.supportFragmentManager
+ }
+
+ @UiThreadTest
+ @Test
+ fun testNavigate() {
+ lateinit var dialogFragment: DialogFragment
+ fragmentManager.fragmentFactory = object : FragmentFactory() {
+ override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+ return super.instantiate(classLoader, className).also { fragment ->
+ if (fragment is DialogFragment) {
+ dialogFragment = fragment
+ }
+ }
+ }
+ }
+ val dialogNavigator = DialogFragmentNavigator(emptyActivity, fragmentManager)
+ val destination = dialogNavigator.createDestination().apply {
+ id = INITIAL_FRAGMENT
+ className = EmptyDialogFragment::class.java.name
+ }
+
+ assertThat(dialogNavigator.navigate(destination, null, null, null))
+ .isEqualTo(destination)
+ fragmentManager.executePendingTransactions()
+ assertWithMessage("Dialog should be shown")
+ .that(dialogFragment.requireDialog().isShowing)
+ .isTrue()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPop() {
+ lateinit var dialogFragment: DialogFragment
+ fragmentManager.fragmentFactory = object : FragmentFactory() {
+ override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+ return super.instantiate(classLoader, className).also { fragment ->
+ if (fragment is DialogFragment) {
+ dialogFragment = fragment
+ }
+ }
+ }
+ }
+ val dialogNavigator = DialogFragmentNavigator(emptyActivity, fragmentManager)
+ val destination = dialogNavigator.createDestination().apply {
+ id = INITIAL_FRAGMENT
+ className = EmptyDialogFragment::class.java.name
+ }
+
+ assertThat(dialogNavigator.navigate(destination, null, null, null))
+ .isEqualTo(destination)
+ fragmentManager.executePendingTransactions()
+ assertWithMessage("Dialog should be shown")
+ .that(dialogFragment.requireDialog().isShowing)
+ .isTrue()
+ assertWithMessage("DialogNavigator should pop dialog off the back stack")
+ .that(dialogNavigator.popBackStack())
+ .isTrue()
+ assertWithMessage("Pop should dismiss the DialogFragment")
+ .that(dialogFragment.requireDialog().isShowing)
+ .isFalse()
+ }
+}
+
+class EmptyDialogFragment : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+ AlertDialog.Builder(requireContext()).create()
+}
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt
index e3dc4c6..e79d900 100644
--- a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/OnBackPressedTest.kt
@@ -65,6 +65,23 @@
@UiThreadTest
@Test
+ fun testOnBackPressedAfterNavigate_notDefaultNavHost() {
+ val activity = activityRule.activity
+ val navController = activity.navController
+ navController.setGraph(R.navigation.nav_simple)
+ navController.navigate(R.id.empty_fragment)
+ activity.supportFragmentManager.beginTransaction()
+ .setPrimaryNavigationFragment(null)
+ .commitNow()
+
+ activity.onBackPressed()
+ assertWithMessage("onBackPressed() should finish the activity when not the primary nav")
+ .that(activity.isFinishing)
+ .isTrue()
+ }
+
+ @UiThreadTest
+ @Test
fun testOnBackPressedWithChildBackStack() {
val activity = activityRule.activity
val navHostFragment = activity.supportFragmentManager.primaryNavigationFragment
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java
index 38da7ab..0fd7840 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.java
@@ -81,7 +81,7 @@
return false;
}
Fragment existingFragment = mFragmentManager
- .findFragmentByTag(DIALOG_TAG + mDialogCount--);
+ .findFragmentByTag(DIALOG_TAG + --mDialogCount);
if (existingFragment != null) {
existingFragment.getLifecycle().removeObserver(mObserver);
((DialogFragment) existingFragment).dismiss();
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index 8a344e9..551f124 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -121,6 +121,8 @@
}
private NavController mNavController;
+ private NavController.NavHostOnBackPressedManager mOnBackPressedManager;
+ private Boolean mIsPrimaryBeforeOnCreate = null;
// State that will be saved and restored
private int mGraphId;
@@ -207,7 +209,13 @@
mNavController = new NavController(context);
mNavController.setHostLifecycleOwner(this);
- mNavController.setHostOnBackPressedDispatcherOwner(requireActivity());
+ mOnBackPressedManager = mNavController
+ .setHostOnBackPressedDispatcherOwner(requireActivity());
+ // Set the default state - this will be updated whenever
+ // onPrimaryNavigationFragmentChanged() is called
+ mOnBackPressedManager.enableOnBackPressed(
+ mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
+ mIsPrimaryBeforeOnCreate = null;
mNavController.setHostViewModelStore(getViewModelStore());
onCreateNavController(mNavController);
@@ -262,6 +270,16 @@
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
+ @CallSuper
+ @Override
+ public void onPrimaryNavigationFragmentChanged(boolean isPrimaryNavigationFragment) {
+ if (mOnBackPressedManager != null) {
+ mOnBackPressedManager.enableOnBackPressed(isPrimaryNavigationFragment);
+ } else {
+ mIsPrimaryBeforeOnCreate = isPrimaryNavigationFragment;
+ }
+ }
+
/**
* Create the FragmentNavigator that this NavHostFragment will use. By default, this uses
* {@link FragmentNavigator}, which replaces the entire contents of the NavHostFragment.
diff --git a/navigation/runtime/api/2.1.0-alpha04.txt b/navigation/runtime/api/2.1.0-alpha04.txt
index 8fe3dc4..8517257 100644
--- a/navigation/runtime/api/2.1.0-alpha04.txt
+++ b/navigation/runtime/api/2.1.0-alpha04.txt
@@ -69,11 +69,15 @@
method @CallSuper public void setGraph(androidx.navigation.NavGraph);
method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
method public void setHostLifecycleOwner(androidx.lifecycle.LifecycleOwner);
- method public void setHostOnBackPressedDispatcherOwner(androidx.activity.OnBackPressedDispatcherOwner);
+ method public androidx.navigation.NavController.NavHostOnBackPressedManager setHostOnBackPressedDispatcherOwner(androidx.activity.OnBackPressedDispatcherOwner);
method public void setHostViewModelStore(androidx.lifecycle.ViewModelStore);
field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
}
+ public static interface NavController.NavHostOnBackPressedManager {
+ method public void enableOnBackPressed(boolean);
+ }
+
public static interface NavController.OnDestinationChangedListener {
method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
}
diff --git a/navigation/runtime/api/current.txt b/navigation/runtime/api/current.txt
index 8fe3dc4..8517257 100644
--- a/navigation/runtime/api/current.txt
+++ b/navigation/runtime/api/current.txt
@@ -69,11 +69,15 @@
method @CallSuper public void setGraph(androidx.navigation.NavGraph);
method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
method public void setHostLifecycleOwner(androidx.lifecycle.LifecycleOwner);
- method public void setHostOnBackPressedDispatcherOwner(androidx.activity.OnBackPressedDispatcherOwner);
+ method public androidx.navigation.NavController.NavHostOnBackPressedManager setHostOnBackPressedDispatcherOwner(androidx.activity.OnBackPressedDispatcherOwner);
method public void setHostViewModelStore(androidx.lifecycle.ViewModelStore);
field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
}
+ public static interface NavController.NavHostOnBackPressedManager {
+ method public void enableOnBackPressed(boolean);
+ }
+
public static interface NavController.OnDestinationChangedListener {
method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
}
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavController.java b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
index 82467e2..4d2dc31 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
@@ -104,6 +104,7 @@
popBackStack();
}
};
+ private boolean mEnableOnBackPressedCallback = true;
/**
* OnDestinationChangedListener receives a callback when the
@@ -125,6 +126,22 @@
}
/**
+ * Interface returned by
+ * {@link #setHostOnBackPressedDispatcherOwner(OnBackPressedDispatcherOwner)} to
+ * allow the {@link NavHost} to manually disable or enable whether the NavController
+ * should actively handle system Back button events.
+ */
+ public interface NavHostOnBackPressedManager {
+ /**
+ * Set whether the NavController should handle the system Back button events via the
+ * registered {@link OnBackPressedDispatcher}.
+ *
+ * @param enabled True if the NavController should handle system Back button events.
+ */
+ void enableOnBackPressed(boolean enabled);
+ }
+
+ /**
* Constructs a new controller for a given {@link Context}. Controllers should not be
* used outside of their context and retain a hard reference to the context supplied.
* If you need a global controller, pass {@link Context#getApplicationContext()}.
@@ -287,7 +304,7 @@
break;
}
}
- mOnBackPressedCallback.setEnabled(getDestinationCountOnBackStack() > 1);
+ updateOnBackPressedCallbackEnabled();
return popped;
}
@@ -485,7 +502,7 @@
}
mBackStack.add(new NavBackStackEntry(uuid, node, args));
}
- mOnBackPressedCallback.setEnabled(getDestinationCountOnBackStack() > 1);
+ updateOnBackPressedCallbackEnabled();
mBackStackUUIDsToRestore = null;
mBackStackIdsToRestore = null;
mBackStackArgsToRestore = null;
@@ -872,7 +889,7 @@
newDest.addInDefaultArgs(finalArgs));
mBackStack.add(newBackStackEntry);
}
- mOnBackPressedCallback.setEnabled(getDestinationCountOnBackStack() > 1);
+ updateOnBackPressedCallbackEnabled();
if (popped || newDest != null) {
dispatchOnDestinationChanged();
}
@@ -1010,9 +1027,14 @@
*
* @param owner The {@link OnBackPressedDispatcherOwner} associated with the containing
* {@link NavHost}.
+ * @return a {@link NavHostOnBackPressedManager} that allows you to enable or disable
+ * whether this NavController should intercept the system Back button events using this
+ * {@link OnBackPressedDispatcher}.
* @see #setHostLifecycleOwner(LifecycleOwner)
*/
- public void setHostOnBackPressedDispatcherOwner(@NonNull OnBackPressedDispatcherOwner owner) {
+ @NonNull
+ public NavHostOnBackPressedManager setHostOnBackPressedDispatcherOwner(
+ @NonNull OnBackPressedDispatcherOwner owner) {
if (mLifecycleOwner == null) {
mLifecycleOwner = owner;
}
@@ -1021,6 +1043,23 @@
mOnBackPressedCallback.remove();
// Then add it to the new dispatcher
dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
+ return new NavHostOnBackPressedManager() {
+ @Override
+ public void enableOnBackPressed(boolean enabled) {
+ setEnableOnBackPressedCallback(enabled);
+ }
+ };
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void setEnableOnBackPressedCallback(boolean enableOnBackPressedCallback) {
+ mEnableOnBackPressedCallback = enableOnBackPressedCallback;
+ updateOnBackPressedCallbackEnabled();
+ }
+
+ private void updateOnBackPressedCallbackEnabled() {
+ mOnBackPressedCallback.setEnabled(mEnableOnBackPressedCallback
+ && getDestinationCountOnBackStack() > 1);
}
/**
diff --git a/palette/build.gradle b/palette/build.gradle
index 5f7cc1f..a537d09 100644
--- a/palette/build.gradle
+++ b/palette/build.gradle
@@ -15,7 +15,7 @@
dependencies {
api(project(":core"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
annotationProcessor(NULLAWAY)
diff --git a/preference/api/1.1.0-alpha06.txt b/preference/api/1.1.0-alpha06.txt
index 0edd4f7b..5afea33 100644
--- a/preference/api/1.1.0-alpha06.txt
+++ b/preference/api/1.1.0-alpha06.txt
@@ -49,7 +49,6 @@
ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!, int);
ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!);
ctor public EditTextPreference(android.content.Context!);
- method public androidx.preference.EditTextPreference.OnBindEditTextListener? getOnBindEditTextListener();
method public String! getText();
method public void setOnBindEditTextListener(androidx.preference.EditTextPreference.OnBindEditTextListener?);
method public void setText(String!);
diff --git a/preference/api/current.txt b/preference/api/current.txt
index 0edd4f7b..5afea33 100644
--- a/preference/api/current.txt
+++ b/preference/api/current.txt
@@ -49,7 +49,6 @@
ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!, int);
ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!);
ctor public EditTextPreference(android.content.Context!);
- method public androidx.preference.EditTextPreference.OnBindEditTextListener? getOnBindEditTextListener();
method public String! getText();
method public void setOnBindEditTextListener(androidx.preference.EditTextPreference.OnBindEditTextListener?);
method public void setText(String!);
diff --git a/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java b/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java
index af6ef57..9091896 100644
--- a/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java
+++ b/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java
@@ -24,7 +24,6 @@
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
import android.text.InputFilter;
import android.widget.EditText;
@@ -76,8 +75,6 @@
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
- assertNull(mEditTextPreference.getOnBindEditTextListener());
-
mEditTextPreference.setOnBindEditTextListener(
new EditTextPreference.OnBindEditTextListener() {
@Override
diff --git a/preference/src/main/java/androidx/preference/EditTextPreference.java b/preference/src/main/java/androidx/preference/EditTextPreference.java
index b597f0c..725c93a 100644
--- a/preference/src/main/java/androidx/preference/EditTextPreference.java
+++ b/preference/src/main/java/androidx/preference/EditTextPreference.java
@@ -158,7 +158,7 @@
* there is no OnBindEditTextListener set
* @see OnBindEditTextListener
*/
- public @Nullable OnBindEditTextListener getOnBindEditTextListener() {
+ @Nullable OnBindEditTextListener getOnBindEditTextListener() {
return mOnBindEditTextListener;
}
diff --git a/recyclerview/selection/build.gradle b/recyclerview/selection/build.gradle
index b63fc0b..7f59362 100644
--- a/recyclerview/selection/build.gradle
+++ b/recyclerview/selection/build.gradle
@@ -27,7 +27,7 @@
api(project(":recyclerview"))
api("androidx.annotation:annotation:1.1.0-rc01")
api(project(":core"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/slices/core/build.gradle b/slices/core/build.gradle
index 615a1f2..8e7c2ad 100644
--- a/slices/core/build.gradle
+++ b/slices/core/build.gradle
@@ -27,7 +27,7 @@
dependencies {
implementation "androidx.annotation:annotation:1.1.0-rc01"
implementation project(":appcompat")
- api project(":collection")
+ api "androidx.collection:collection:1.1.0-rc01"
api(project(":remotecallback"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
index 0ee765c..92d9bd9 100644
--- a/slices/view/build.gradle
+++ b/slices/view/build.gradle
@@ -26,7 +26,7 @@
dependencies {
implementation(project(":slice-core"))
implementation(project(":recyclerview"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
api(ARCH_LIFECYCLE_LIVEDATA_CORE, libs.exclude_annotations_transitive)
androidTestImplementation(project(":slice-builders"))
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/LocaleTestUtilsTest.kt b/testutils/src/androidTest/java/androidx/testutils/LocaleTestUtilsTest.kt
similarity index 74%
rename from viewpager2/src/androidTest/java/androidx/viewpager2/LocaleTestUtilsTest.kt
rename to testutils/src/androidTest/java/androidx/testutils/LocaleTestUtilsTest.kt
index a10fd14..433d6de 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/LocaleTestUtilsTest.kt
+++ b/testutils/src/androidTest/java/androidx/testutils/LocaleTestUtilsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2019 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 androidx.viewpager2
+package androidx.testutils
import android.content.Context
import android.content.res.Configuration
@@ -25,10 +25,10 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers
import org.junit.After
-import org.junit.Assert.assertThat
+import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,7 +40,8 @@
@RunWith(AndroidJUnit4::class)
@LargeTest
class LocaleTestUtilsTest {
- private val configuration: Configuration get() =
+ private val configuration: Configuration
+ get() =
(ApplicationProvider.getApplicationContext() as Context).resources.configuration
private val Configuration.language: String get() =
ConfigurationCompat.getLocales(this).get(0).toString()
@@ -51,7 +52,8 @@
@Before
fun setUp() {
localeUtil = LocaleTestUtils(
- ApplicationProvider.getApplicationContext() as android.content.Context)
+ ApplicationProvider.getApplicationContext() as Context
+ )
determineDefaultLayoutDirection()
}
@@ -84,12 +86,24 @@
val getReason: (String, String) -> String = { name, code ->
"$name test language '$code' does not exist on test device"
}
- assertThat(getReason("Default", LocaleTestUtils.DEFAULT_TEST_LANGUAGE),
- LocaleTestUtils.DEFAULT_TEST_LANGUAGE, Matchers.isIn(availableLanguages))
- assertThat(getReason("LTR", LocaleTestUtils.LTR_LANGUAGE),
- LocaleTestUtils.LTR_LANGUAGE, Matchers.isIn(availableLanguages))
- assertThat(getReason("RTL", LocaleTestUtils.RTL_LANGUAGE),
- LocaleTestUtils.RTL_LANGUAGE, Matchers.isIn(availableLanguages))
+ Assert.assertThat(
+ getReason(
+ "Default",
+ LocaleTestUtils.DEFAULT_TEST_LANGUAGE
+ ),
+ LocaleTestUtils.DEFAULT_TEST_LANGUAGE,
+ Matchers.isIn(availableLanguages)
+ )
+ Assert.assertThat(
+ getReason("LTR", LocaleTestUtils.LTR_LANGUAGE),
+ LocaleTestUtils.LTR_LANGUAGE,
+ Matchers.isIn(availableLanguages)
+ )
+ Assert.assertThat(
+ getReason("RTL", LocaleTestUtils.RTL_LANGUAGE),
+ LocaleTestUtils.RTL_LANGUAGE,
+ Matchers.isIn(availableLanguages)
+ )
}
private fun assertDefaultValues() {
@@ -97,25 +111,25 @@
}
private fun assertLocaleIs(lang: String, expectRtl: Boolean) {
- assertThat(
+ Assert.assertThat(
"Locale should be $lang",
configuration.language,
- equalTo(lang)
+ CoreMatchers.equalTo(lang)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- assertThat(
+ Assert.assertThat(
"Layout direction should be ${if (expectRtl) "RTL" else "LTR"}",
configuration.layoutDirection,
- equalTo(if (expectRtl) LAYOUT_DIRECTION_RTL else LAYOUT_DIRECTION_LTR)
+ CoreMatchers.equalTo(if (expectRtl) LAYOUT_DIRECTION_RTL else LAYOUT_DIRECTION_LTR)
)
}
}
private fun determineDefaultLayoutDirection() {
- assertThat(
+ Assert.assertThat(
"Locale must still be the default when determining the default layout direction",
configuration.language,
- equalTo(DEFAULT_LANGUAGE)
+ CoreMatchers.equalTo(DEFAULT_LANGUAGE)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
expectRtlInDefaultLanguage = configuration.layoutDirection == LAYOUT_DIRECTION_RTL
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/LocaleTestUtils.kt b/testutils/src/main/java/androidx/testutils/LocaleTestUtils.kt
similarity index 98%
rename from viewpager2/src/androidTest/java/androidx/viewpager2/LocaleTestUtils.kt
rename to testutils/src/main/java/androidx/testutils/LocaleTestUtils.kt
index 6ca6392..e2e4c4e 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/LocaleTestUtils.kt
+++ b/testutils/src/main/java/androidx/testutils/LocaleTestUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2019 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 androidx.viewpager2
+package androidx.testutils
import android.content.Context
import android.content.res.Configuration
diff --git a/ui/android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt b/ui/android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt
index e4b2611..333e390 100644
--- a/ui/android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt
+++ b/ui/android-view/src/main/java/androidx/ui/androidview/WebComponentActivity.kt
@@ -54,7 +54,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContent @Composable {
+ setContent {
if (WebContext.debug) {
Log.e("WebCompAct", "setContent")
}
diff --git a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt
index 17fad61..bcb86e2 100644
--- a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt
+++ b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt
@@ -89,7 +89,7 @@
recompose()
}
}, (200..800).random().toLong())
- Transition(definition = definition, toState = toState) @Composable { state ->
+ Transition(definition = definition, toState = toState) { state ->
DrawColorRectState(state = state)
}
}
diff --git a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt
index 2b10222..a098e3c 100644
--- a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt
+++ b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt
@@ -76,7 +76,7 @@
toState.value = ComponentState.Released },
toState.value = ComponentState.Released }) {
val children = @Composable {
- Transition(definition = definition, toState = toState.value) @Composable { state ->
+ Transition(definition = definition, toState = toState.value) { state ->
DrawScaledRect(scale=state[scale], color=state[color])
}
}
diff --git a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/RepeatedRotationActivity.kt b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/RepeatedRotationActivity.kt
index c331d7f..f0836cf 100644
--- a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/RepeatedRotationActivity.kt
+++ b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/RepeatedRotationActivity.kt
@@ -73,7 +73,7 @@
Transition(
definition = definition,
toState = state.value
- ) @Composable { state ->
+ ) { state ->
Draw { canvas, parentSize ->
canvas.save()
canvas.rotate(radians(state[rotation]))
diff --git a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/StateBasedRippleAnimation.kt b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/StateBasedRippleAnimation.kt
index ac21c95..333bdd7 100644
--- a/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/StateBasedRippleAnimation.kt
+++ b/ui/animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/StateBasedRippleAnimation.kt
@@ -93,7 +93,7 @@
}
PressGestureDetector( {
val children = @Composable {
- Transition(definition = rippleTransDef, toState = toState) @Composable { state ->
+ Transition(definition = rippleTransDef, toState = toState) { state ->
RippleRectFromState(state = state)
}
}
diff --git a/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt b/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt
index d1c9504..2011e3b 100644
--- a/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt
+++ b/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt
@@ -161,11 +161,11 @@
@Suppress("FunctionName")
@Composable
private fun Animation(animationEndState: ComponentState) {
- Layout(children = @Composable {
+ Layout(children = {
Transition(
definition = transitionDefinition,
toState = animationEndState
- ) @Composable { state ->
+ ) { state ->
Circle(color = state[colorKey], sizeRatio = state[sizeKey])
}
}, layoutBlock = { _, constraints ->
diff --git a/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt b/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
index f9ac57b..96f4de0 100644
--- a/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
+++ b/ui/framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/VectorGraphicsActivity.kt
@@ -38,7 +38,7 @@
super.onCreate(savedInstanceState)
val res = getResources()
- setContent @Composable {
+ setContent {
composer.registerAdapter { parent, child ->
adoptVectorGraphic(parent, child)
}
diff --git a/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt b/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
index 5f406e8..55bfbd8 100644
--- a/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
+++ b/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
@@ -278,7 +278,7 @@
runOnUiThread {
activity.setContent {
CraneWrapper {
- WithConstraints @Composable { constraints ->
+ WithConstraints { constraints ->
topConstraints.value = constraints
Padding(size = size) {
WithConstraints { constraints ->
@@ -400,7 +400,7 @@
paint.color = model.outerColor
canvas.drawRect(parentSize.toRect(), paint)
}
- Layout(children = @Composable {
+ Layout(children = {
AtLeastSize(size = model.size) {
Draw { canvas, parentSize ->
drawLatch.countDown()
@@ -563,7 +563,7 @@
CraneWrapper {
Draw(children = {
AtLeastSize(size = (model.size * 3)) {
- Draw(children = @Composable {
+ Draw(children = {
Draw { canvas, parentSize ->
val paint = Paint()
paint.color = model.innerColor
diff --git a/ui/framework/src/main/java/androidx/ui/core/Layout.kt b/ui/framework/src/main/java/androidx/ui/core/Layout.kt
index b93cd0d..a599cbb 100644
--- a/ui/framework/src/main/java/androidx/ui/core/Layout.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/Layout.kt
@@ -380,7 +380,7 @@
val addMarkers = childrenArray.size > 1
childrenArray.forEach { childrenComposable ->
childrenComposable()
- if (addMarkers) ChildrenEndMarker(p1 = childrenComposable)
+ if (addMarkers) ChildrenEndMarker(childrenComposable)
}
}
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
index f6d84fa..7a2f924 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
@@ -53,7 +53,7 @@
val alignPosition = Ref<PxPosition>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.BottomRight) {
SaveLayoutInfo(
size = alignSize,
@@ -93,7 +93,7 @@
val alignPosition = Ref<PxPosition>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Layout(
children = {
Align(alignment = Alignment.BottomRight) {
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/ConstrainedBoxTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/ConstrainedBoxTest.kt
index 77fc1c8..9642a1a 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/ConstrainedBoxTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/ConstrainedBoxTest.kt
@@ -50,7 +50,7 @@
val constrainedBoxSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
constrainedBoxSize.value = coordinates.size
@@ -84,7 +84,7 @@
val constrainedBoxSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
Container(width = sizeDp, height = sizeDp) {
OnChildPositioned( coordinates ->
@@ -120,7 +120,7 @@
val positionedLatch = CountDownLatch(1)
val constrainedBoxSize = Ref<PxSize>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
constrainedBoxSize.value = coordinates.size
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt
index 5269ca9..4109f34 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt
@@ -61,7 +61,7 @@
val positionedLatch = CountDownLatch(1)
val containerSize = Ref<PxSize>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
containerSize.value = coordinates.size
@@ -88,7 +88,7 @@
val positionedLatch = CountDownLatch(2)
val containerSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
containerSize.value = coordinates.size
@@ -127,7 +127,7 @@
val positionedLatch = CountDownLatch(4)
val containerSize = Ref<PxSize>()
val childSize = Array(3) { PxSize(0.px, 0.px) }
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
containerSize.value = coordinates.size
@@ -187,7 +187,7 @@
val containerSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnPositioned( coordinates ->
alignSize.value = coordinates.size
@@ -231,7 +231,7 @@
val containerSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
containerSize.value = coordinates.size
@@ -270,7 +270,7 @@
val containerSize = Ref<PxSize>()
val latch = CountDownLatch(1)
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
Container(width = sizeDp, height = sizeDp, padding = EdgeInsets(10.dp)) {
OnPositioned( coordinates ->
@@ -303,7 +303,7 @@
var containerSize: PxSize? = null
val latch = CountDownLatch(1)
- show @Composable {
+ show {
Wrap {
Container(padding = edgeInsets) {
FixedSpacer(width = childSizeDp, height = childSizeDp)
@@ -328,7 +328,7 @@
var childCoordinates: LayoutCoordinates? = null
val latch = CountDownLatch(1)
- show @Composable {
+ show {
Wrap {
Container(width = containerSize, height = containerSize, padding = edgeInsets) {
OnChildPositioned( coordinates ->
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
index 19bd6e1..77ce33b 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/FlexTest.kt
@@ -60,7 +60,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Row {
Container(width = sizeDp, height = sizeDp) {
@@ -109,7 +109,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
FlexRow {
val widthDp = 50.px.toDp()
@@ -172,7 +172,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
FlexRow {
flexible(flex = 1f) {
@@ -228,7 +228,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Column {
Container(width = sizeDp, height = sizeDp) {
@@ -276,7 +276,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
FlexColumn {
val heightDp = 50.px.toDp()
@@ -339,7 +339,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
FlexColumn {
flexible(flex = 1f) {
@@ -395,7 +395,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Row(crossAxisAlignment = CrossAxisAlignment.Start) {
Container(width = sizeDp, height = sizeDp) {
@@ -444,7 +444,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Row(crossAxisAlignment = CrossAxisAlignment.End) {
Container(width = sizeDp, height = sizeDp) {
@@ -496,7 +496,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Row(crossAxisAlignment = CrossAxisAlignment.Stretch) {
Container(width = sizeDp, height = sizeDp) {
@@ -539,7 +539,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Column(crossAxisAlignment = CrossAxisAlignment.Start) {
Container(width = sizeDp, height = sizeDp) {
@@ -588,7 +588,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Column(crossAxisAlignment = CrossAxisAlignment.End) {
Container(width = sizeDp, height = sizeDp) {
@@ -641,7 +641,7 @@
val drawLatch = CountDownLatch(2)
val childSize = arrayOf(PxSize(-1.px, -1.px), PxSize(-1.px, -1.px))
val childPosition = arrayOf(PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px))
- show @Composable {
+ show {
Center {
Column(crossAxisAlignment = CrossAxisAlignment.Stretch) {
Container(width = sizeDp, height = sizeDp) {
@@ -682,7 +682,7 @@
val drawLatch = CountDownLatch(1)
lateinit var rowSize: PxSize
- show @Composable {
+ show {
Center {
Row(mainAxisSize = MainAxisSize.Max) {
FixedSpacer(width = sizeDp, height = sizeDp)
@@ -713,7 +713,7 @@
val drawLatch = CountDownLatch(1)
lateinit var rowSize: PxSize
- show @Composable {
+ show {
Center {
Row(mainAxisSize = MainAxisSize.Min) {
FixedSpacer(width = sizeDp, height = sizeDp)
@@ -745,7 +745,7 @@
val drawLatch = CountDownLatch(1)
lateinit var rowSize: PxSize
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints(minWidth = rowWidthDp)) {
Row(mainAxisSize = MainAxisSize.Min) {
@@ -781,7 +781,7 @@
val drawLatch = CountDownLatch(2)
lateinit var rowSize: PxSize
lateinit var expandedChildSize: PxSize
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints(minWidth = rowWidthDp)) {
FlexRow(mainAxisSize = MainAxisSize.Min) {
@@ -824,7 +824,7 @@
val drawLatch = CountDownLatch(1)
lateinit var columnSize: PxSize
- show @Composable {
+ show {
Center {
Column(mainAxisSize = MainAxisSize.Max) {
FixedSpacer(width = sizeDp, height = sizeDp)
@@ -855,7 +855,7 @@
val drawLatch = CountDownLatch(1)
lateinit var columnSize: PxSize
- show @Composable {
+ show {
Center {
Column(mainAxisSize = MainAxisSize.Min) {
FixedSpacer(width = sizeDp, height = sizeDp)
@@ -889,7 +889,7 @@
val drawLatch = CountDownLatch(2)
lateinit var columnSize: PxSize
lateinit var expandedChildSize: PxSize
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints(minHeight = columnHeightDp)) {
FlexColumn(mainAxisSize = MainAxisSize.Min) {
@@ -934,7 +934,7 @@
val drawLatch = CountDownLatch(1)
lateinit var columnSize: PxSize
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints(minHeight = columnHeightDp)) {
Column(mainAxisSize = MainAxisSize.Min) {
@@ -970,7 +970,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Row(mainAxisAlignment = MainAxisAlignment.Start) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1012,7 +1012,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Row(mainAxisAlignment = MainAxisAlignment.End) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1054,7 +1054,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Row(mainAxisAlignment = MainAxisAlignment.Center) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1097,7 +1097,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Row(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1140,7 +1140,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Row(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1183,7 +1183,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Row(mainAxisAlignment = MainAxisAlignment.SpaceAround) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1226,7 +1226,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Column(mainAxisAlignment = MainAxisAlignment.Start) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1268,7 +1268,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Column(mainAxisAlignment = MainAxisAlignment.End) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1310,7 +1310,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Column(mainAxisAlignment = MainAxisAlignment.Center) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1353,7 +1353,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1396,7 +1396,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Column(mainAxisAlignment = MainAxisAlignment.SpaceBetween) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1439,7 +1439,7 @@
PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px), PxPosition(-1.px, -1.px)
)
val childLayoutCoordinates = arrayOfNulls<LayoutCoordinates?>(childPosition.size)
- show @Composable {
+ show {
Center {
Column(mainAxisAlignment = MainAxisAlignment.SpaceAround) {
// TODO(popam): replace with normal for loop when IR is fixed
@@ -1480,7 +1480,7 @@
val drawLatch = CountDownLatch(4)
val containerSize = Ref<PxSize>()
- show @Composable {
+ show {
Center {
ConstrainedBox(
constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt
index 06e5b45..67e7bb3 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/OnPositionedTest.kt
@@ -48,7 +48,7 @@
var realTop: Px? = null
val drawLatch = CountDownLatch(1)
- show @Composable {
+ show {
Padding(left = paddingLeftPx.toDp(), top = paddingTopPx.toDp()) {
Container(expanded = true) {
OnPositioned(>
@@ -73,7 +73,7 @@
var realTop: Px? = null
val drawLatch = CountDownLatch(1)
- show @Composable {
+ show {
Padding(left = paddingLeftPx.toDp(), top = paddingTopPx.toDp()) {
OnChildPositioned(>
realLeft = it.position.x
@@ -99,7 +99,7 @@
var childCoordinates: LayoutCoordinates? = null
val drawLatch = CountDownLatch(2)
- show @Composable {
+ show {
Padding(left = firstPaddingPx.toDp()) {
Padding(left = secondPaddingPx.toDp()) {
OnPositioned(>
@@ -137,7 +137,7 @@
var secondCoordinates: LayoutCoordinates? = null
val drawLatch = CountDownLatch(2)
- show @Composable {
+ show {
Row {
OnChildPositioned(>
firstCoordinates = it
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/PaddingTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/PaddingTest.kt
index da6979d..cd91a7c 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/PaddingTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/PaddingTest.kt
@@ -149,7 +149,7 @@
val drawLatch = CountDownLatch(1)
var childSize = PxSize(-1.px, -1.px)
var childPosition = PxPosition(-1.px, -1.px)
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)) {
val children = @Composable {
@@ -162,7 +162,7 @@
})
}
}
- paddingContainer(p1 = children)
+ paddingContainer(children)
}
}
}
@@ -194,7 +194,7 @@
val drawLatch = CountDownLatch(1)
var childSize = PxSize(-1.px, -1.px)
var childPosition = PxPosition(-1.px, -1.px)
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)) {
val children = @Composable {
@@ -207,7 +207,7 @@
})
}
}
- paddingContainer(p1 = children)
+ paddingContainer(children)
}
}
}
@@ -247,7 +247,7 @@
val drawLatch = CountDownLatch(1)
var childSize = PxSize(-1.px, -1.px)
var childPosition = PxPosition(-1.px, -1.px)
- show @Composable {
+ show {
Center {
ConstrainedBox(constraints = DpConstraints.tightConstraints(sizeDp, sizeDp)) {
val children = @Composable {
@@ -260,7 +260,7 @@
})
}
}
- paddingContainer(p1 = children)
+ paddingContainer(children)
}
}
}
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/SpacerTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/SpacerTest.kt
index b6446a2..147af44 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/SpacerTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/SpacerTest.kt
@@ -54,7 +54,7 @@
val height = 71.dp
val drawLatch = CountDownLatch(1)
- show @Composable {
+ show {
Container(constraints = bigConstraints) {
OnChildPositioned( position ->
size = position.size
@@ -81,7 +81,7 @@
val drawLatch = CountDownLatch(1)
val containerWidth = 5.dp
val containerHeight = 7.dp
- show @Composable {
+ show {
Center {
Container(
constraints = DpConstraints(
@@ -112,7 +112,7 @@
val width = 71.dp
val drawLatch = CountDownLatch(1)
- show @Composable {
+ show {
Container(constraints = bigConstraints) {
OnChildPositioned( position ->
size = position.size
@@ -138,7 +138,7 @@
val drawLatch = CountDownLatch(1)
val containerWidth = 5.dp
val containerHeight = 7.dp
- show @Composable {
+ show {
Center {
Container(
constraints = DpConstraints(
@@ -169,7 +169,7 @@
val height = 7.dp
val drawLatch = CountDownLatch(1)
- show @Composable {
+ show {
Container(constraints = bigConstraints) {
OnChildPositioned( position ->
size = position.size
@@ -195,7 +195,7 @@
val drawLatch = CountDownLatch(1)
val containerWidth = 5.dp
val containerHeight = 7.dp
- show @Composable {
+ show {
Center {
Container(
constraints = DpConstraints(
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/StackTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/StackTest.kt
index 8282148..3e04720 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/StackTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/StackTest.kt
@@ -52,7 +52,7 @@
val alignedChildPosition = Ref<PxPosition>()
val positionedChildSize = Ref<PxSize>()
val positionedChildPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
stackSize.value = coordinates.size
@@ -106,7 +106,7 @@
val stackSize = Ref<PxSize>()
val childSize = arrayOf(Ref<PxSize>(), Ref<PxSize>())
val childPosition = arrayOf(Ref<PxPosition>(), Ref<PxPosition>())
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
stackSize.value = coordinates.size
@@ -155,7 +155,7 @@
val stackSize = Ref<PxSize>()
val childSize = Array(7) { Ref<PxSize>() }
val childPosition = Array(7) { Ref<PxPosition>() }
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
stackSize.value = coordinates.size
diff --git a/ui/layout/src/androidTest/java/androidx/ui/layout/test/WrapTest.kt b/ui/layout/src/androidTest/java/androidx/ui/layout/test/WrapTest.kt
index caa9ecc..f670aeb 100644
--- a/ui/layout/src/androidTest/java/androidx/ui/layout/test/WrapTest.kt
+++ b/ui/layout/src/androidTest/java/androidx/ui/layout/test/WrapTest.kt
@@ -51,7 +51,7 @@
val wrapSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
wrapSize.value = coordinates.size
@@ -87,7 +87,7 @@
val wrapSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
wrapSize.value = coordinates.size
@@ -127,7 +127,7 @@
val wrapSize = Ref<PxSize>()
val childSize = Ref<PxSize>()
val childPosition = Ref<PxPosition>()
- show @Composable {
+ show {
Align(alignment = Alignment.TopLeft) {
OnChildPositioned( coordinates ->
wrapSize.value = coordinates.size
diff --git a/ui/layout/src/main/java/androidx/ui/layout/Flex.kt b/ui/layout/src/main/java/androidx/ui/layout/Flex.kt
index b801a00..a6c8832 100644
--- a/ui/layout/src/main/java/androidx/ui/layout/Flex.kt
+++ b/ui/layout/src/main/java/androidx/ui/layout/Flex.kt
@@ -45,7 +45,7 @@
if (flex < 0) {
throw IllegalArgumentException("flex must be >= 0")
}
- childrenList += @Composable {
+ childrenList += {
ParentData(data = FlexInfo(flex = flex, fit = FlexFit.Tight), children = children)
}
}
@@ -54,13 +54,13 @@
if (flex < 0) {
throw IllegalArgumentException("flex must be >= 0")
}
- childrenList += @Composable {
+ childrenList += {
ParentData(data = FlexInfo(flex = flex, fit = FlexFit.Loose), children = children)
}
}
fun inflexible(children: @Composable() () -> Unit) {
- childrenList += @Composable {
+ childrenList += {
ParentData(data = FlexInfo(flex = 0f, fit = FlexFit.Loose), children = children)
}
}
@@ -485,7 +485,7 @@
fun Placeable.mainAxisSize() = if (orientation == FlexOrientation.Horizontal) width else height
fun Placeable.crossAxisSize() = if (orientation == FlexOrientation.Horizontal) height else width
- val flexChildren: @Composable() () -> Unit = with(FlexChildren()) @Composable {
+ val flexChildren: @Composable() () -> Unit = with(FlexChildren()) {
block()
val composable = @Composable {
childrenList.forEach { it() }
diff --git a/ui/layout/src/main/java/androidx/ui/layout/Stack.kt b/ui/layout/src/main/java/androidx/ui/layout/Stack.kt
index 9e3637a..d1d8e18 100644
--- a/ui/layout/src/main/java/androidx/ui/layout/Stack.kt
+++ b/ui/layout/src/main/java/androidx/ui/layout/Stack.kt
@@ -54,12 +54,12 @@
leftInset = leftInset, topInset = topInset,
rightInset = rightInset, bottomInset = bottomInset
)
- _stackChildren += @Composable { ParentData(data = data, children = children) }
+ _stackChildren += { ParentData(data = data, children = children) }
}
fun aligned(alignment: Alignment, children: @Composable() () -> Unit) {
val data = StackChildData(alignment = alignment)
- _stackChildren += @Composable { ParentData(data = data, children = children) }
+ _stackChildren += { ParentData(data = data, children = children) }
}
}
diff --git a/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleActivity.kt b/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleActivity.kt
index f066088..f00954e 100644
--- a/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleActivity.kt
+++ b/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleActivity.kt
@@ -34,8 +34,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // TODO: remove @Composable annotation here when b/131681875 is fixed
- setContent @Composable {
+ setContent {
val layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
0,
diff --git a/ui/material/src/main/java/androidx/ui/material/Checkbox.kt b/ui/material/src/main/java/androidx/ui/material/Checkbox.kt
index a8b63e4..a30d7a3 100644
--- a/ui/material/src/main/java/androidx/ui/material/Checkbox.kt
+++ b/ui/material/src/main/java/androidx/ui/material/Checkbox.kt
@@ -94,8 +94,7 @@
val definition = +memo(activeColor, unselectedColor) {
generateTransitionDefinition(activeColor, unselectedColor)
}
- // TODO: remove @Composable annotation here when b/131681875 is fixed
- Transition(definition = definition, toState = value) @Composable { state ->
+ Transition(definition = definition, toState = value) { state ->
DrawBox(
color = state[BoxColorProp],
innerRadiusFraction = state[InnerRadiusFractionProp]
diff --git a/ui/material/src/main/java/androidx/ui/material/RadioButton.kt b/ui/material/src/main/java/androidx/ui/material/RadioButton.kt
index e51952d..2b468bd 100644
--- a/ui/material/src/main/java/androidx/ui/material/RadioButton.kt
+++ b/ui/material/src/main/java/androidx/ui/material/RadioButton.kt
@@ -211,8 +211,7 @@
val definition = +memo(selectedColor, unselectedColor) {
generateTransitionDefinition(selectedColor, unselectedColor)
}
- // TODO: remove @Composable annotation here when b/131681875 is fixed
- Transition(definition = definition, toState = selected) @Composable { state ->
+ Transition(definition = definition, toState = selected) { state ->
DrawRadioButton(
color = state[ColorProp],
outerRadius = state[OuterRadiusProp],
diff --git a/ui/material/src/main/java/androidx/ui/material/Switch.kt b/ui/material/src/main/java/androidx/ui/material/Switch.kt
index a5d48c7..80d22d4 100644
--- a/ui/material/src/main/java/androidx/ui/material/Switch.kt
+++ b/ui/material/src/main/java/androidx/ui/material/Switch.kt
@@ -84,8 +84,7 @@
(+themeColor { onSurface }).withOpacity(UncheckedTrackOpacity)
}
DrawTrack(color = trackColor)
- // TODO: remove @Composable annotation here when b/131681875 is fixed
- Transition(definition = transDef, toState = checked) @Composable { state ->
+ Transition(definition = transDef, toState = checked) { state ->
DrawThumb(
color = state[ThumbColorProp],
relativePosition = state[RelativeThumbTranslationProp]
diff --git a/ui/test/src/main/java/androidx/ui/test/android/AndroidUiTestRunner.kt b/ui/test/src/main/java/androidx/ui/test/android/AndroidUiTestRunner.kt
index fb2f95c..abc21e1 100644
--- a/ui/test/src/main/java/androidx/ui/test/android/AndroidUiTestRunner.kt
+++ b/ui/test/src/main/java/androidx/ui/test/android/AndroidUiTestRunner.kt
@@ -175,7 +175,7 @@
private fun setContentInternal(composable: @Composable() () -> Unit) {
activity.setContentView(FrameLayout(activity).apply {
- compositionContext = Compose.composeInto(this, null, composable = @Composable {
+ compositionContext = Compose.composeInto(this, null, composable = {
TestWrapper {
composable()
}
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index df96767..1408eb4 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -27,7 +27,7 @@
dependencies {
api(project(":fragment"))
api(project(":recyclerview"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.1.0-rc01")
androidTestImplementation(project(":appcompat"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index 42d38bc..ba7dfdb 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -41,7 +41,7 @@
import androidx.test.rule.ActivityTestRule
import androidx.testutils.AppCompatActivityUtils
import androidx.testutils.FragmentActivityUtils.waitForActivityDrawn
-import androidx.viewpager2.LocaleTestUtils
+import androidx.testutils.LocaleTestUtils
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.test.R
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
@@ -84,7 +84,8 @@
@Before
open fun setUp() {
localeUtil = LocaleTestUtils(
- ApplicationProvider.getApplicationContext() as android.content.Context)
+ ApplicationProvider.getApplicationContext() as android.content.Context
+ )
// Ensure a predictable test environment by explicitly setting a locale
localeUtil.setLocale(LocaleTestUtils.DEFAULT_TEST_LANGUAGE)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index c3e93b9..a7a4e17 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -24,7 +24,7 @@
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.test.filters.LargeTest
import androidx.testutils.FragmentActivityUtils.waitForCycles
-import androidx.viewpager2.LocaleTestUtils
+import androidx.testutils.LocaleTestUtils
import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrollStateChangedEvent
import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrolledEvent
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
index 6eab611..8fae152 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
@@ -20,7 +20,7 @@
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.test.filters.LargeTest
-import androidx.viewpager2.LocaleTestUtils
+import androidx.testutils.LocaleTestUtils
import androidx.viewpager2.widget.OffscreenPageLimitTest.Event.OnChildViewAdded
import androidx.viewpager2.widget.OffscreenPageLimitTest.Event.OnChildViewRemoved
import androidx.viewpager2.widget.OffscreenPageLimitTest.Event.OnPageScrollStateChangedEvent
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index fc0b31a..9dfefab 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -22,9 +22,9 @@
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
+import androidx.testutils.LocaleTestUtils
import androidx.testutils.PollingCheck
import androidx.viewpager.widget.ViewPager
-import androidx.viewpager2.LocaleTestUtils
import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.MarkerEvent
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.OnPageScrollStateChangedEvent
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
index 411285e..3b53100 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
@@ -17,8 +17,8 @@
package androidx.viewpager2.widget.swipe
import android.os.Bundle
+import androidx.testutils.LocaleTestUtils
import androidx.testutils.RecreatedAppCompatActivity
-import androidx.viewpager2.LocaleTestUtils
import androidx.viewpager2.test.R
class TestActivity : RecreatedAppCompatActivity() {
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestApplication.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestApplication.java
index 9720882..3af4c26 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestApplication.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestApplication.java
@@ -22,6 +22,8 @@
import androidx.annotation.NonNull;
import androidx.work.Configuration;
+import java.util.concurrent.Executors;
+
/**
* An Application class that initializes WorkManager.
*/
@@ -30,6 +32,8 @@
@NonNull
@Override
public Configuration getWorkManagerConfiguration() {
- return new Configuration.Builder().setMinimumLoggingLevel(Log.VERBOSE).build();
+ return new Configuration.Builder()
+ .setTaskExecutor(Executors.newCachedThreadPool())
+ .setMinimumLoggingLevel(Log.VERBOSE).build();
}
}
diff --git a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
index 1948994..c67dfd2 100644
--- a/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
+++ b/work/workmanager-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
@@ -75,10 +75,6 @@
mExecutor.execute(runnable)
}
- override fun getBackgroundExecutorThread(): Thread {
- return Thread.currentThread()
- }
-
override fun getBackgroundExecutor(): Executor {
return mExecutor
}
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
index 79a8825..f20e6f0 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -157,10 +157,6 @@
override fun getBackgroundExecutor(): Executor {
return mSynchronousExecutor
}
-
- override fun getBackgroundExecutorThread(): Thread {
- return Thread.currentThread()
- }
}
class SynchronousCoroutineWorker(context: Context, params: WorkerParameters) :
diff --git a/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt b/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
index 722cf4e..54c9f47 100644
--- a/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
+++ b/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
@@ -154,9 +154,5 @@
override fun getBackgroundExecutor(): Executor {
return mSynchronousExecutor
}
-
- override fun getBackgroundExecutorThread(): Thread {
- return Thread.currentThread()
- }
}
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java b/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java
index 6ef57d0..f727fd7 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/InstantWorkTaskExecutor.java
@@ -53,10 +53,4 @@
public Executor getBackgroundExecutor() {
return mSynchronousExecutor;
}
-
- @NonNull
- @Override
- public Thread getBackgroundExecutorThread() {
- return Thread.currentThread();
- }
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
index 434668f..d5cc128 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
@@ -74,12 +74,6 @@
public Executor getBackgroundExecutor() {
return mSynchronousExecutor;
}
-
- @NonNull
- @Override
- public Thread getBackgroundExecutorThread() {
- return Thread.currentThread();
- }
},
true);
diff --git a/work/workmanager/api/2.1.0-alpha01.txt b/work/workmanager/api/2.1.0-alpha01.txt
index 2c2382c..7b667ab 100644
--- a/work/workmanager/api/2.1.0-alpha01.txt
+++ b/work/workmanager/api/2.1.0-alpha01.txt
@@ -15,6 +15,7 @@
method public java.util.concurrent.Executor getExecutor();
method public int getMaxJobSchedulerId();
method public int getMinJobSchedulerId();
+ method public java.util.concurrent.Executor getTaskExecutor();
method public androidx.work.WorkerFactory getWorkerFactory();
field public static final int MIN_SCHEDULER_LIMIT = 20; // 0x14
}
@@ -26,6 +27,7 @@
method public androidx.work.Configuration.Builder setJobSchedulerJobIdRange(int, int);
method public androidx.work.Configuration.Builder setMaxSchedulerLimit(int);
method public androidx.work.Configuration.Builder setMinimumLoggingLevel(int);
+ method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor);
method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory);
}
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index 2c2382c..7b667ab 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -15,6 +15,7 @@
method public java.util.concurrent.Executor getExecutor();
method public int getMaxJobSchedulerId();
method public int getMinJobSchedulerId();
+ method public java.util.concurrent.Executor getTaskExecutor();
method public androidx.work.WorkerFactory getWorkerFactory();
field public static final int MIN_SCHEDULER_LIMIT = 20; // 0x14
}
@@ -26,6 +27,7 @@
method public androidx.work.Configuration.Builder setJobSchedulerJobIdRange(int, int);
method public androidx.work.Configuration.Builder setMaxSchedulerLimit(int);
method public androidx.work.Configuration.Builder setMinimumLoggingLevel(int);
+ method public androidx.work.Configuration.Builder setTaskExecutor(java.util.concurrent.Executor);
method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory);
}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/DatabaseTest.java b/work/workmanager/src/androidTest/java/androidx/work/DatabaseTest.java
index 6448bb9..ca3a800 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/DatabaseTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/DatabaseTest.java
@@ -22,6 +22,8 @@
import org.junit.After;
import org.junit.Before;
+import java.util.concurrent.Executors;
+
/**
* An abstract class for getting an in-memory instance of the {@link WorkDatabase}.
*/
@@ -30,7 +32,10 @@
@Before
public void initializeDb() {
- mDatabase = WorkDatabase.create(ApplicationProvider.getApplicationContext(), true);
+ mDatabase = WorkDatabase.create(
+ ApplicationProvider.getApplicationContext(),
+ Executors.newCachedThreadPool(),
+ true);
}
@After
diff --git a/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java b/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
index ce8a2cb..332139c 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
@@ -34,6 +34,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Executor;
+
@RunWith(AndroidJUnit4.class)
public class DefaultWorkerFactoryTest extends DatabaseTest {
@@ -52,6 +54,7 @@
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
insertWork(work);
+ Executor executor = new SynchronousExecutor();
ListenableWorker worker = mDefaultWorkerFactory.createWorkerWithDefaultFallback(
mContext.getApplicationContext(),
TestWorker.class.getName(),
@@ -61,8 +64,8 @@
work.getTags(),
new WorkerParameters.RuntimeExtras(),
1,
- new SynchronousExecutor(),
- new WorkManagerTaskExecutor(),
+ executor,
+ new WorkManagerTaskExecutor(executor),
mDefaultWorkerFactory));
assertThat(worker, is(notNullValue()));
assertThat(worker,
diff --git a/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt b/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
index 5026071..d84a892 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
+++ b/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
@@ -86,7 +86,7 @@
WorkerParameters.RuntimeExtras(),
1,
SynchronousExecutor(),
- WorkManagerTaskExecutor(),
+ WorkManagerTaskExecutor(SynchronousExecutor()),
factory
)
}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
index d8613a1..c5b095e 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
@@ -101,7 +101,7 @@
});
Context context = ApplicationProvider.getApplicationContext();
- mDatabase = WorkDatabase.create(context, true);
+ mDatabase = WorkDatabase.create(context, Executors.newCachedThreadPool(), true);
InstantWorkTaskExecutor taskExecutor = new InstantWorkTaskExecutor();
Configuration configuration = new Configuration.Builder()
.setExecutor(Executors.newSingleThreadExecutor())
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/SerialExecutorTest.kt b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/SerialExecutorTest.kt
new file mode 100644
index 0000000..53b872d
--- /dev/null
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/SerialExecutorTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 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 androidx.work.impl.utils
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.greaterThanOrEqualTo
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class SerialExecutorTest {
+
+ lateinit var executor: SerialExecutor
+
+ @Before
+ fun setUp() {
+ executor = SerialExecutor(Executors.newCachedThreadPool())
+ }
+
+ @Test
+ fun testSerialExecutor() {
+ val latch = CountDownLatch(3)
+ val first = TimestampTrackingRunnable(latch)
+ val second = TimestampTrackingRunnable(latch)
+ val third = TimestampTrackingRunnable(latch)
+ val commands = listOf(first, second, third)
+ commands.forEach(executor::execute)
+ latch.await(1, TimeUnit.SECONDS)
+ var lastStart = 0L
+ for (runnable in commands) {
+ assertThat(runnable.start, greaterThanOrEqualTo(lastStart))
+ lastStart = runnable.end
+ }
+ }
+
+ companion object {
+ class TimestampTrackingRunnable(private val latch: CountDownLatch) : Runnable {
+ var start: Long = 0
+ var end: Long = 0
+ override fun run() {
+ start = System.nanoTime()
+ val sleepTime = (Math.random() * 100).toLong()
+ try {
+ // Sleep for a random amount of time to simulate real work.
+ Thread.sleep(sleepTime)
+ } catch (exception: InterruptedException) {
+ throw RuntimeException(exception)
+ } finally {
+ end = System.nanoTime()
+ latch.countDown()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
index 84b4abc..8bfee8e 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
@@ -16,7 +16,6 @@
package androidx.work.impl.utils.taskexecutor;
-import androidx.annotation.NonNull;
import androidx.work.impl.utils.SynchronousExecutor;
import java.util.concurrent.Executor;
@@ -47,10 +46,4 @@
public Executor getBackgroundExecutor() {
return mSynchronousExecutor;
}
-
- @NonNull
- @Override
- public Thread getBackgroundExecutorThread() {
- return Thread.currentThread();
- }
}
diff --git a/work/workmanager/src/main/java/androidx/work/Configuration.java b/work/workmanager/src/main/java/androidx/work/Configuration.java
index 33c362b..1350bf4 100644
--- a/work/workmanager/src/main/java/androidx/work/Configuration.java
+++ b/work/workmanager/src/main/java/androidx/work/Configuration.java
@@ -49,6 +49,7 @@
public static final int MIN_SCHEDULER_LIMIT = 20;
private final @NonNull Executor mExecutor;
+ private final @NonNull Executor mTaskExecutor;
private final @NonNull WorkerFactory mWorkerFactory;
private final int mLoggingLevel;
private final int mMinJobSchedulerId;
@@ -62,6 +63,15 @@
mExecutor = builder.mExecutor;
}
+ if (builder.mTaskExecutor == null) {
+ // This executor is used for *both* WorkManager's tasks and Room's query executor.
+ // So this should not be a single threaded executor. Writes will still be serialized
+ // as this will be wrapped with an SerialExecutor.
+ mTaskExecutor = createDefaultExecutor();
+ } else {
+ mTaskExecutor = builder.mTaskExecutor;
+ }
+
if (builder.mWorkerFactory == null) {
mWorkerFactory = WorkerFactory.getDefaultWorkerFactory();
} else {
@@ -82,6 +92,14 @@
}
/**
+ * @return The {@link Executor} used by {@link WorkManager} for all its internal book-keeping.
+ */
+ @NonNull
+ public Executor getTaskExecutor() {
+ return mTaskExecutor;
+ }
+
+ /**
* @return The {@link WorkerFactory} used by {@link WorkManager} to create
* {@link ListenableWorker}s
*/
@@ -151,6 +169,8 @@
Executor mExecutor;
WorkerFactory mWorkerFactory;
+ Executor mTaskExecutor;
+
int mLoggingLevel = Log.INFO;
int mMinJobSchedulerId = IdGenerator.INITIAL_ID;
int mMaxJobSchedulerId = Integer.MAX_VALUE;
@@ -179,6 +199,25 @@
}
/**
+ * Specifies a {@link Executor} which will be used by WorkManager for all its
+ * internal book-keeping.
+ *
+ * For best performance this {@link Executor} should be bounded. This {@link Executor}
+ * should *not* be a single threaded executor.
+ *
+ * For more information look at
+ * {@link androidx.room.RoomDatabase.Builder#setQueryExecutor(Executor)}.
+ *
+ * @param taskExecutor The {@link Executor} which will be used by WorkManager for
+ * all its internal book-keeping
+ * @return This {@link Builder} instance
+ */
+ public @NonNull Builder setTaskExecutor(@NonNull Executor taskExecutor) {
+ mTaskExecutor = taskExecutor;
+ return this;
+ }
+
+ /**
* Specifies the range of {@link android.app.job.JobInfo} IDs that can be used by
* {@link WorkManager}. WorkManager needs a range of at least {@code 1000} IDs.
* <p>
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
index ec4f505..8a783c8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -48,6 +48,7 @@
import androidx.work.impl.model.WorkTagDao;
import androidx.work.impl.model.WorkTypeConverters;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -91,17 +92,23 @@
* Creates an instance of the WorkDatabase.
*
* @param context A context (this method will use the application context from it)
+ * @param queryExecutor An {@link Executor} that will be used to execute all async Room
+ * queries.
* @param useTestDatabase {@code true} to generate an in-memory database that allows main thread
* access
* @return The created WorkDatabase
*/
- public static WorkDatabase create(Context context, boolean useTestDatabase) {
+ public static WorkDatabase create(
+ @NonNull Context context,
+ @NonNull Executor queryExecutor,
+ boolean useTestDatabase) {
RoomDatabase.Builder<WorkDatabase> builder;
if (useTestDatabase) {
builder = Room.inMemoryDatabaseBuilder(context, WorkDatabase.class)
.allowMainThreadQueries();
} else {
- builder = Room.databaseBuilder(context, WorkDatabase.class, DB_NAME);
+ builder = Room.databaseBuilder(context, WorkDatabase.class, DB_NAME)
+ .setQueryExecutor(queryExecutor);
}
return builder.addCallback(generateCleanupCallback())
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index db19bdc..a500e80 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -179,7 +179,7 @@
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
- new WorkManagerTaskExecutor());
+ new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
}
sDelegatedInstance = sDefaultInstance;
}
@@ -224,7 +224,8 @@
boolean useTestDatabase) {
Context applicationContext = context.getApplicationContext();
- WorkDatabase database = WorkDatabase.create(applicationContext, useTestDatabase);
+ WorkDatabase database = WorkDatabase.create(
+ applicationContext, configuration.getTaskExecutor(), useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List<Scheduler> schedulers = createSchedulers(applicationContext);
Processor processor = new Processor(
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index f482cf0..eb4e9b7 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -312,7 +312,6 @@
// Package-private for synthetic accessor.
void onWorkFinished() {
- assertBackgroundExecutorThread();
boolean isWorkFinished = false;
if (!tryCheckForInterruptionAndResolve()) {
try {
@@ -566,13 +565,6 @@
}
}
- private void assertBackgroundExecutorThread() {
- if (mWorkTaskExecutor.getBackgroundExecutorThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Needs to be executed on the Background executor thread.");
- }
- }
-
private String createWorkDescription(List<String> tags) {
StringBuilder sb = new StringBuilder("Work [ id=")
.append(mWorkSpecId)
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java
new file mode 100644
index 0000000..90e9adc
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/SerialExecutor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2019 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 androidx.work.impl.utils;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link Executor} which delegates to another {@link Executor} but ensures that tasks are
+ * executed serially, like a single threaded executor.
+ */
+public class SerialExecutor implements Executor {
+ private final ArrayDeque<Task> mTasks;
+ private final Executor mExecutor;
+ private final Object mLock;
+ private volatile Runnable mActive;
+
+ public SerialExecutor(@NonNull Executor executor) {
+ mExecutor = executor;
+ mTasks = new ArrayDeque<>();
+ mLock = new Object();
+ }
+
+ @Override
+ public void execute(@NonNull Runnable command) {
+ synchronized (mLock) {
+ mTasks.add(new Task(this, command));
+ if (mActive == null) {
+ scheduleNext();
+ }
+ }
+ }
+
+ // Synthetic access
+ void scheduleNext() {
+ synchronized (mLock) {
+ if ((mActive = mTasks.poll()) != null) {
+ mExecutor.execute(mActive);
+ }
+ }
+ }
+
+ /**
+ * A {@link Runnable} which tells the {@link SerialExecutor} to schedule the next command
+ * after completion.
+ */
+ static class Task implements Runnable {
+ final SerialExecutor mSerialExecutor;
+ final Runnable mRunnable;
+
+ Task(@NonNull SerialExecutor serialExecutor, @NonNull Runnable runnable) {
+ mSerialExecutor = serialExecutor;
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mRunnable.run();
+ } finally {
+ mSerialExecutor.scheduleNext();
+ }
+ }
+ }
+}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
index 056442d..e5df2d9 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
@@ -16,7 +16,6 @@
package androidx.work.impl.utils.taskexecutor;
-import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import java.util.concurrent.Executor;
@@ -45,12 +44,6 @@
void executeOnBackgroundThread(Runnable runnable);
/**
- * @return the {@link Thread} being used by WorkManager's background task executor.
- */
- @NonNull
- Thread getBackgroundExecutorThread();
-
- /**
* @return The {@link Executor} for background task processing
*/
Executor getBackgroundExecutor();
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
index ef2eb6a..d689d89 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
@@ -21,20 +21,25 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
+import androidx.work.impl.utils.SerialExecutor;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
/**
* Default Task Executor for executing common tasks in WorkManager
* @hide
*/
-
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerTaskExecutor implements TaskExecutor {
+ private final Executor mBackgroundExecutor;
+
+ public WorkManagerTaskExecutor(@NonNull Executor backgroundExecutor) {
+ // Wrap it with a serial executor so we have ordering guarantees on commands
+ // being executed.
+ mBackgroundExecutor = new SerialExecutor(backgroundExecutor);
+ }
+
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final Executor mMainThreadExecutor = new Executor() {
@@ -44,26 +49,6 @@
}
};
- // Avoiding synthetic accessor.
- volatile Thread mCurrentBackgroundExecutorThread;
- private final ThreadFactory mBackgroundThreadFactory = new ThreadFactory() {
-
- private int mThreadsCreated = 0;
-
- @Override
- public Thread newThread(@NonNull Runnable r) {
- // Delegate to the default factory, but keep track of the current thread being used.
- Thread thread = Executors.defaultThreadFactory().newThread(r);
- thread.setName("WorkManager-WorkManagerTaskExecutor-thread-" + mThreadsCreated);
- mThreadsCreated++;
- mCurrentBackgroundExecutorThread = thread;
- return thread;
- }
- };
-
- private final ExecutorService mBackgroundExecutor =
- Executors.newSingleThreadExecutor(mBackgroundThreadFactory);
-
@Override
public void postToMainThread(Runnable r) {
mMainThreadHandler.post(r);
@@ -83,10 +68,4 @@
public Executor getBackgroundExecutor() {
return mBackgroundExecutor;
}
-
- @NonNull
- @Override
- public Thread getBackgroundExecutorThread() {
- return mCurrentBackgroundExecutorThread;
- }
}