Merge "Fix flaky FragmentScenarioTest.fragmentWithOptionsMenu" into androidx-master-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index d43bbf3..89ab155 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -40,6 +40,7 @@
const val KOTLINPOET = "com.squareup:kotlinpoet:1.0.0"
const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:1.3.0"
const val KOTLIN_METADATA = "me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0"
+const val KOTLIN_METADATA_JVM = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.0.5"
const val KOTLIN_COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0"
const val LEAKCANARY_INSTRUMENTATION =
"com.squareup.leakcanary:leakcanary-android-instrumentation:1.6.2"
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
index fb260b2..a52ef71 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
@@ -17,6 +17,11 @@
package androidx.fragment.app;
+import static androidx.fragment.app.FragmentTestUtil.HostCallbacks;
+import static androidx.fragment.app.FragmentTestUtil.restartFragmentController;
+import static androidx.fragment.app.FragmentTestUtil.shutdownFragmentController;
+import static androidx.fragment.app.FragmentTestUtil.startupFragmentController;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -44,20 +49,17 @@
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
import android.widget.TextView;
import androidx.annotation.ContentView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.app.ActivityCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.test.EmptyFragmentTestActivity;
import androidx.fragment.app.test.FragmentTestActivity;
import androidx.fragment.test.R;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ViewModelStore;
-import androidx.lifecycle.ViewModelStoreOwner;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
@@ -70,8 +72,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -343,7 +343,8 @@
public void testSavedInstanceStateAfterRestore() {
final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = startupFragmentController(null, viewModelStore);
+ final FragmentController fc1 =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm1 = fc1.getSupportFragmentManager();
// Add the initial state
@@ -369,7 +370,8 @@
fc1.dispatchDestroy();
// Create the new controller and restore state
- final FragmentController fc2 = startupFragmentController(savedState, viewModelStore);
+ final FragmentController fc2 =
+ startupFragmentController(mActivityRule.getActivity(), savedState, viewModelStore);
final FragmentManager fm2 = fc2.getSupportFragmentManager();
final StrictFragment restoredParentFragment = (StrictFragment) fm2
@@ -694,7 +696,8 @@
@UiThreadTest
public void saveAnimationState() throws Throwable {
ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(null, viewModelStore);
+ FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
+ viewModelStore);
FragmentManager fm = fc.getSupportFragmentManager();
fm.beginTransaction()
@@ -707,7 +710,7 @@
assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
// Causes save and restore of fragments and back stack
- fc = restartFragmentController(fc, viewModelStore);
+ fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
fm = fc.getSupportFragmentManager();
assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
@@ -722,7 +725,7 @@
assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
// Causes save and restore of fragments and back stack
- fc = restartFragmentController(fc, viewModelStore);
+ fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
fm = fc.getSupportFragmentManager();
assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
@@ -841,7 +844,8 @@
@UiThreadTest
public void fragmentDestroyedOnFinish() throws Throwable {
ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(null, viewModelStore);
+ FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
+ viewModelStore);
FragmentManager fm = fc.getSupportFragmentManager();
StrictViewFragment fragmentA = StrictViewFragment.create(R.layout.fragment_a);
@@ -882,7 +886,7 @@
public void run() {
final ViewModelStore viewModelStore = new ViewModelStore();
final FragmentController fc1 = startupFragmentController(
- null, viewModelStore);
+ mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm1 = fc1.getSupportFragmentManager();
@@ -924,7 +928,8 @@
// is being restored as the fragment controller state is being brought up.
try {
- startupFragmentController(savedState, viewModelStore);
+ startupFragmentController(mActivityRule.getActivity(), savedState,
+ viewModelStore);
fail("Expected IllegalStateException when moving from "
+ StrictFragment.stateToString(fromState) + " to "
@@ -944,14 +949,15 @@
@UiThreadTest
public void noPrematureStateChange() throws Throwable {
ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(null, viewModelStore);
+ FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
+ viewModelStore);
FragmentManager fm = fc.getSupportFragmentManager();
fm.beginTransaction()
.add(new StrictFragment(), "1")
.commitNow();
- fc = restartFragmentController(fc, viewModelStore);
+ fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
fm = fc.getSupportFragmentManager();
@@ -971,7 +977,8 @@
@UiThreadTest
public void testIsStateSaved() throws Throwable {
ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(null, viewModelStore);
+ FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
+ viewModelStore);
FragmentManager fm = fc.getSupportFragmentManager();
Fragment f = new StrictFragment();
@@ -1000,7 +1007,8 @@
@UiThreadTest
public void testSetArgumentsLifecycle() throws Throwable {
ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(null, viewModelStore);
+ FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
+ viewModelStore);
FragmentManager fm = fc.getSupportFragmentManager();
Fragment f = new StrictFragment();
@@ -1077,7 +1085,8 @@
fc1.execPendingActions();
// Simulate an activity restart
- final FragmentController fc2 = restartFragmentController(fc1, viewModelStore);
+ final FragmentController fc2 =
+ restartFragmentController(mActivityRule.getActivity(), fc1, viewModelStore);
// Bring the state back down to destroyed before we finish the test
shutdownFragmentController(fc2, viewModelStore);
@@ -1116,7 +1125,8 @@
fc1.execPendingActions();
// Simulate an activity restart
- final FragmentController fc2 = restartFragmentController(fc1, viewModelStore);
+ final FragmentController fc2 =
+ restartFragmentController(mActivityRule.getActivity(), fc1, viewModelStore);
// Bring the state back down to destroyed before we finish the test
shutdownFragmentController(fc2, viewModelStore);
@@ -1126,7 +1136,8 @@
@UiThreadTest
public void targetFragmentClearedWhenSetToNull() {
ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = startupFragmentController(null, viewModelStore);
+ final FragmentController fc =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm = fc.getSupportFragmentManager();
@@ -1169,7 +1180,8 @@
@UiThreadTest
public void targetFragmentOnlyTargetAdded() {
ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = startupFragmentController(null, viewModelStore);
+ final FragmentController fc =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm = fc.getSupportFragmentManager();
@@ -1209,7 +1221,8 @@
@UiThreadTest
public void targetFragmentNonRetainedNonRetained() {
ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = startupFragmentController(null, viewModelStore);
+ final FragmentController fc =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm = fc.getSupportFragmentManager();
@@ -1250,7 +1263,8 @@
@UiThreadTest
public void targetFragmentRetainedNonRetained() {
ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = startupFragmentController(null, viewModelStore);
+ final FragmentController fc =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm = fc.getSupportFragmentManager();
@@ -1292,7 +1306,8 @@
@UiThreadTest
public void targetFragmentNonRetainedRetained() {
ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = startupFragmentController(null, viewModelStore);
+ final FragmentController fc =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm = fc.getSupportFragmentManager();
@@ -1330,7 +1345,8 @@
@UiThreadTest
public void targetFragmentRetainedRetained() {
ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = startupFragmentController(null, viewModelStore);
+ final FragmentController fc =
+ startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
final FragmentManager fm = fc.getSupportFragmentManager();
@@ -1760,38 +1776,6 @@
Assert.assertEquals(popExit, record.mPopExitAnim);
}
- private FragmentController restartFragmentController(FragmentController fc,
- ViewModelStore viewModelStore) {
- Parcelable savedState = shutdownFragmentController(fc, viewModelStore);
- return startupFragmentController(savedState, viewModelStore);
- }
-
- private FragmentController startupFragmentController(Parcelable savedState,
- ViewModelStore viewModelStore) {
- final FragmentController fc = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
- fc.attachHost(null);
- fc.restoreSaveState(savedState);
- fc.dispatchCreate();
- fc.dispatchActivityCreated();
- fc.noteStateNotSaved();
- fc.execPendingActions();
- fc.dispatchStart();
- fc.dispatchResume();
- fc.execPendingActions();
- return fc;
- }
-
- private Parcelable shutdownFragmentController(FragmentController fc,
- ViewModelStore viewModelStore) {
- fc.dispatchPause();
- final Parcelable savedState = fc.saveAllState();
- fc.dispatchStop();
- viewModelStore.clear();
- fc.dispatchDestroy();
- return savedState;
- }
-
private void executePendingTransactions(final FragmentManager fm) throws Throwable {
mActivityRule.runOnUiThread(new Runnable() {
@Override
@@ -1902,100 +1886,6 @@
}
}
- static class HostCallbacks extends FragmentHostCallback<FragmentActivity>
- implements ViewModelStoreOwner {
- private final FragmentActivity mActivity;
- private final ViewModelStore mViewModelStore;
-
- HostCallbacks(FragmentActivity activity, ViewModelStore viewModelStore) {
- super(activity);
- mActivity = activity;
- mViewModelStore = viewModelStore;
- }
-
- @NonNull
- @Override
- public ViewModelStore getViewModelStore() {
- return mViewModelStore;
- }
-
- @Override
- public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- }
-
- @Override
- public boolean onShouldSaveFragmentState(Fragment fragment) {
- return !mActivity.isFinishing();
- }
-
- @Override
- @NonNull
- public LayoutInflater onGetLayoutInflater() {
- return mActivity.getLayoutInflater().cloneInContext(mActivity);
- }
-
- @Override
- public FragmentActivity onGetHost() {
- return mActivity;
- }
-
- @Override
- public void onSupportInvalidateOptionsMenu() {
- mActivity.supportInvalidateOptionsMenu();
- }
-
- @Override
- public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
- mActivity.startActivityFromFragment(fragment, intent, requestCode);
- }
-
- @Override
- public void onStartActivityFromFragment(
- Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
- mActivity.startActivityFromFragment(fragment, intent, requestCode, options);
- }
-
- @Override
- public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
- @NonNull String[] permissions, int requestCode) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
- return ActivityCompat.shouldShowRequestPermissionRationale(
- mActivity, permission);
- }
-
- @Override
- public boolean onHasWindowAnimations() {
- return mActivity.getWindow() != null;
- }
-
- @Override
- public int onGetWindowAnimations() {
- final Window w = mActivity.getWindow();
- return (w == null) ? 0 : w.getAttributes().windowAnimations;
- }
-
- @Override
- public void onAttachFragment(Fragment fragment) {
- mActivity.onAttachFragment(fragment);
- }
-
- @Nullable
- @Override
- public View onFindViewById(int id) {
- return mActivity.findViewById(id);
- }
-
- @Override
- public boolean onHasView() {
- final Window w = mActivity.getWindow();
- return (w != null && w.peekDecorView() != null);
- }
- }
-
public static class SimpleFragment extends Fragment {
private int mLayoutId;
private static final String LAYOUT_ID = "layoutId";
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.java
index f220673..5dad36f 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.java
@@ -19,23 +19,36 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.content.Intent;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
import android.view.animation.Animation;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
import androidx.fragment.app.test.FragmentTestActivity;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
public class FragmentTestUtil {
+
private static final Runnable DO_NOTHING = new Runnable() {
@Override
public void run() {
@@ -155,7 +168,8 @@
@Override
public void run() {
Handler handler = new Handler();
- HostCallbacks hostCallbacks = new HostCallbacks(activity, handler, 0);
+ androidx.fragment.app.HostCallbacks hostCallbacks =
+ new androidx.fragment.app.HostCallbacks(activity, handler, 0);
controller[0] = FragmentController.createController(hostCallbacks);
}
});
@@ -241,4 +255,130 @@
}
}
}
+
+ static class HostCallbacks extends FragmentHostCallback<FragmentActivity>
+ implements ViewModelStoreOwner {
+ private final FragmentActivity mActivity;
+ private final ViewModelStore mViewModelStore;
+
+ HostCallbacks(FragmentActivity activity, ViewModelStore viewModelStore) {
+ super(activity);
+ mActivity = activity;
+ mViewModelStore = viewModelStore;
+ }
+
+ @NonNull
+ @Override
+ public ViewModelStore getViewModelStore() {
+ return mViewModelStore;
+ }
+
+ @Override
+ public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ }
+
+ @Override
+ public boolean onShouldSaveFragmentState(Fragment fragment) {
+ return !mActivity.isFinishing();
+ }
+
+ @Override
+ @NonNull
+ public LayoutInflater onGetLayoutInflater() {
+ return mActivity.getLayoutInflater().cloneInContext(mActivity);
+ }
+
+ @Override
+ public FragmentActivity onGetHost() {
+ return mActivity;
+ }
+
+ @Override
+ public void onSupportInvalidateOptionsMenu() {
+ mActivity.supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
+ mActivity.startActivityFromFragment(fragment, intent, requestCode);
+ }
+
+ @Override
+ public void onStartActivityFromFragment(
+ Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
+ mActivity.startActivityFromFragment(fragment, intent, requestCode, options);
+ }
+
+ @Override
+ public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
+ @NonNull String[] permissions, int requestCode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
+ return ActivityCompat.shouldShowRequestPermissionRationale(
+ mActivity, permission);
+ }
+
+ @Override
+ public boolean onHasWindowAnimations() {
+ return mActivity.getWindow() != null;
+ }
+
+ @Override
+ public int onGetWindowAnimations() {
+ final Window w = mActivity.getWindow();
+ return (w == null) ? 0 : w.getAttributes().windowAnimations;
+ }
+
+ @Override
+ public void onAttachFragment(Fragment fragment) {
+ mActivity.onAttachFragment(fragment);
+ }
+
+ @Nullable
+ @Override
+ public View onFindViewById(int id) {
+ return mActivity.findViewById(id);
+ }
+
+ @Override
+ public boolean onHasView() {
+ final Window w = mActivity.getWindow();
+ return (w != null && w.peekDecorView() != null);
+ }
+ }
+
+ public static FragmentController startupFragmentController(FragmentActivity activity,
+ Parcelable savedState, ViewModelStore viewModelStore) {
+ final FragmentController fc = FragmentController.createController(
+ new HostCallbacks(activity, viewModelStore));
+ fc.attachHost(null);
+ fc.restoreSaveState(savedState);
+ fc.dispatchCreate();
+ fc.dispatchActivityCreated();
+ fc.noteStateNotSaved();
+ fc.execPendingActions();
+ fc.dispatchStart();
+ fc.dispatchResume();
+ fc.execPendingActions();
+ return fc;
+ }
+
+ public static FragmentController restartFragmentController(FragmentActivity activity,
+ FragmentController fc, ViewModelStore viewModelStore) {
+ Parcelable savedState = shutdownFragmentController(fc, viewModelStore);
+ return startupFragmentController(activity, savedState, viewModelStore);
+ }
+
+ public static Parcelable shutdownFragmentController(FragmentController fc,
+ ViewModelStore viewModelStore) {
+ fc.dispatchPause();
+ final Parcelable savedState = fc.saveAllState();
+ fc.dispatchStop();
+ viewModelStore.clear();
+ fc.dispatchDestroy();
+ return savedState;
+ }
}
diff --git a/jetifier/jetifier/source-transformer/rewritePackageNames.py b/jetifier/jetifier/source-transformer/rewritePackageNames.py
index 99326f9..448e092 100755
--- a/jetifier/jetifier/source-transformer/rewritePackageNames.py
+++ b/jetifier/jetifier/source-transformer/rewritePackageNames.py
@@ -7,22 +7,11 @@
# import androidx.annotation.RequiresApi;
# See also b/74074903
+import argparse
import json
import os.path
import subprocess
-HARDCODED_RULES_REVERSE = [
- "s|androidx.core.os.ResultReceiver|android.support.v4.os.ResultsReceiver|g\n",
- "s|androidx.core.media.MediaBrowserCompat|android.support.v4.media.MediaBrowserCompat|g\n",
- "s|androidx.core.media.MediaDescriptionCompat|android.support.v4.media.MediaDescriptionCompat|g\n",
- "s|androidx.core.media.MediaMetadataCompat|android.support.v4.media.MediaMetadataCompat|g\n",
- "s|androidx.core.media.RatingCompat|android.support.v4.media.RatingCompat|g\n",
- "s|androidx.core.media.session.MediaControllerCompat|android.support.v4.media.session.MediaControllerCompat|g\n",
- "s|androidx.core.media.session.MediaSessionCompat|android.support.v4.media.session.MediaSessionCompat|g\n",
- "s|androidx.core.media.session.ParcelableVolumeInfo|android.support.v4.media.session.ParcelableVolumeInfo|g\n",
- "s|androidx.core.media.session.PlaybackStateCompat|android.support.v4.media.session.PlaybackStateCompat|g\n",
-]
-
class StringBuilder(object):
def __init__(self, item=None):
self.items = []
@@ -36,17 +25,6 @@
def __str__(self):
return "".join(self.items)
-class ExecutionConfig(object):
- """Stores configuration about this execution of the package renaming.
- For example, file paths of the source code.
- This config could potentially be affected by command-line arguments.
- """
- def __init__(self, jetifierConfig, sourceRoots, excludeDirs):
- self.jetifierConfig = jetifierConfig
- self.sourceRoots = sourceRoots
- self.excludeDirs = excludeDirs
-
-
class SourceRewriteRule(object):
def __init__(self, fromName, toName):
self.fromName = fromName
@@ -56,8 +34,8 @@
return self.fromName + ":" + self.toName
class JetifierConfig(object):
- """Stores configuration about the renaming itself, such as package rename rules.
- This config isn't supposed to be affected by command-line arguments.
+ """
+ Stores configuration about the renaming itself, such as package rename rules.
"""
@staticmethod
def parse(filePath):
@@ -66,78 +44,63 @@
nonCommentLines = [line for line in lines if not line.strip().startswith("#")]
parsed = json.loads("".join(nonCommentLines))
return JetifierConfig(parsed)
-
+
def __init__(self, parsedJson):
self.json = parsedJson
- def getTypesMap(self):
+ def getTypesMap(self, reverse):
rules = []
for rule in self.json["rules"]:
fromName = rule["from"].replace("/", ".").replace("(.*)", "")
toName = rule["to"].replace("/", ".").replace("{0}", "")
if not toName.startswith("ignore"):
- rules.append(SourceRewriteRule(fromName, toName))
+ if reverse:
+ # Dejetify instead, so toName becomes fromName and vice versa.
+ rules.append(SourceRewriteRule(toName, fromName))
+ else:
+ rules.append(SourceRewriteRule(fromName, toName))
return rules
-def createRewriteCommand(executionConfig):
- # create command to find source files
- finderTextBuilder = StringBuilder("find")
- for sourceRoot in executionConfig.sourceRoots:
- finderTextBuilder.add(" ").add(sourceRoot)
- for exclusion in executionConfig.excludeDirs:
- finderTextBuilder.add(" -name ").add(exclusion).add(" -prune -o")
- finderTextBuilder.add(" -iregex '.*\.java\|.*\.xml\|.*\.cfg\|.*\.flags' -print")
-
- # create command to rewrite one source
- print("Building sed instructions")
+def createSourceJetificationSedCommand(args, jetifierConfig):
rewriterTextBuilder = StringBuilder()
- rewriteRules = executionConfig.jetifierConfig.getTypesMap()
+ rewriteRules = jetifierConfig.getTypesMap(args.reverse)
+ # Append substitution rules, this will most probably never exceed the shell
+ # characters per line limit which should be 131072.
+ # In the weird case where the user somehow modified their ARG_MAX,
+ # they'll know what to do when they see a /bin/sed: Argument list too long
+ # error.
for rule in rewriteRules:
- rewriterTextBuilder.add("s|").add(rule.fromName.replace(".", "\.")).add("|").add(rule.toName).add("|g\n")
- for rule in HARDCODED_RULES_REVERSE:
- rewriterTextBuilder.add(rule)
- scriptPath = "/tmp/jetifier-sed-script.txt"
- print("Writing " + scriptPath)
- with open(scriptPath, 'w') as scriptFile:
- scriptFile.write(str(rewriterTextBuilder))
-
- # create the command to do the rewrites
- fullCommand = "time " + str(finderTextBuilder) + " | xargs -n 1 --no-run-if-empty -P 64 sed -i -f " + scriptPath
+ rewriterTextBuilder.add("-e \'s|").add(rule.fromName.replace(".", "\.")).add("|").add(rule.toName).add("|g\' ")
- return fullCommand
+ # sed command containing substitutions and applied to the output file.
+ sedCommand = "sed %s %s > %s" % (rewriterTextBuilder, args.infile, args.outfile)
+ return sedCommand
-def processConfig(executionConfig):
- print("Building rewrite command")
- rewriteCommand = createRewriteCommand(executionConfig)
- commandLength = len(rewriteCommand)
- print("""
-Will run command:
-
-""" + rewriteCommand + """
-
-""")
- response = raw_input("Ok? [y/n]")
- if response == "y":
- subprocess.check_output(rewriteCommand, shell=True)
+def jetifySource(args):
+ # If config file is not specified, look for the config file in the
+ # same folder.
+ jetifierConfigPath = args.config
+ if not jetifierConfigPath:
+ jetifierConfigPath = os.path.join(os.path.realpath(__file__), "default.config")
+ jetifierConfig = JetifierConfig.parse(jetifierConfigPath)
+ command = createSourceJetificationSedCommand(args, jetifierConfig)
+ subprocess.check_output(command, shell=True)
def main():
- pathOfThisFile = os.path.realpath(__file__)
- jetifierPath = os.path.abspath(os.path.join(pathOfThisFile, "..", ".."))
+ # Set up input arguments
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-c", "--config", help="path to optional custom config file.")
+ parser.add_argument("-r", "--reverse", help="operate in reverse mode (\"de-jetification\")",
+ action="store_true")
+ parser.add_argument("-i", "--infile",
+ required=True, help="path to input source (java or xml)")
+ parser.add_argument("-o", "--outfile",
+ required=True, help="path to the output file, overriden if exists.")
+ args = parser.parse_args()
+ jetifySource(args)
- jetifierConfigPath = os.path.join(jetifierPath, "core/src/main/resources", "default.generated.config")
- print("Parsing " + jetifierConfigPath)
- jetifierConfig = JetifierConfig.parse(jetifierConfigPath)
-
- sourceRoot = os.getcwd()
- excludeDirs = ["out", ".git", ".repo"]
-
- executionConfig = ExecutionConfig(jetifierConfig, [sourceRoot], excludeDirs)
-
- processConfig(executionConfig)
-
-main()
-
-
+if __name__ == "__main__":
+ main()
diff --git a/media/api/1.1.0-alpha02.txt b/media/api/1.1.0-alpha02.txt
index c4e14c0..881051e 100644
--- a/media/api/1.1.0-alpha02.txt
+++ b/media/api/1.1.0-alpha02.txt
@@ -285,6 +285,7 @@
method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
method public abstract void sendCustomAction(String!, android.os.Bundle!);
method public abstract void setCaptioningEnabled(boolean);
+ method public void setPlaybackSpeed(float);
method public abstract void setRating(android.support.v4.media.RatingCompat!);
method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
method public abstract void setRepeatMode(int);
@@ -363,6 +364,7 @@
method public void onRewind();
method public void onSeekTo(long);
method public void onSetCaptioningEnabled(boolean);
+ method public void onSetPlaybackSpeed(float);
method public void onSetRating(android.support.v4.media.RatingCompat!);
method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
method public void onSetRepeatMode(int);
diff --git a/media/api/current.txt b/media/api/current.txt
index c4e14c0..881051e 100644
--- a/media/api/current.txt
+++ b/media/api/current.txt
@@ -285,6 +285,7 @@
method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
method public abstract void sendCustomAction(String!, android.os.Bundle!);
method public abstract void setCaptioningEnabled(boolean);
+ method public void setPlaybackSpeed(float);
method public abstract void setRating(android.support.v4.media.RatingCompat!);
method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
method public abstract void setRepeatMode(int);
@@ -363,6 +364,7 @@
method public void onRewind();
method public void onSeekTo(long);
method public void onSetCaptioningEnabled(boolean);
+ method public void onSetPlaybackSpeed(float);
method public void onSetRating(android.support.v4.media.RatingCompat!);
method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
method public void onSetRepeatMode(int);
diff --git a/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl b/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
index f32f449..0fcd811 100644
--- a/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
+++ b/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
@@ -34,7 +34,7 @@
* @hide
*/
interface IMediaSession {
- // Next ID: 48
+ // Next ID: 49
void sendCommand(String command, in Bundle args, in MediaSessionCompat.ResultReceiverWrapper cb) = 0;
boolean sendMediaButton(in KeyEvent mediaButton) = 1;
void registerCallbackListener(in IMediaControllerCallback cb) = 2;
@@ -81,6 +81,7 @@
void seekTo(long pos) = 23;
void rate(in RatingCompat rating) = 24;
void rateWithExtras(in RatingCompat rating, in Bundle extras) = 50;
+ void setPlaybackSpeed(float speed) = 48;
void setCaptioningEnabled(boolean enabled) = 45;
void setRepeatMode(int repeatMode) = 38;
void setShuffleModeEnabledRemoved(boolean shuffleMode) = 39;
diff --git a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
index e7963eb..49956a5 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -1301,6 +1301,13 @@
public abstract void setRating(RatingCompat rating, Bundle extras);
/**
+ * Set the playback speed.
+ *
+ * @param speed The playback speed
+ */
+ public void setPlaybackSpeed(float speed) {}
+
+ /**
* Enables/disables captioning for this session.
*
* @param enabled {@code true} to enable captioning, {@code false} to disable.
@@ -1938,6 +1945,15 @@
}
@Override
+ public void setPlaybackSpeed(float speed) {
+ try {
+ mBinder.setPlaybackSpeed(speed);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in setPlaybackSpeed.", e);
+ }
+ }
+
+ @Override
public void setCaptioningEnabled(boolean enabled) {
try {
mBinder.setCaptioningEnabled(enabled);
@@ -2429,6 +2445,13 @@
}
@Override
+ public void setPlaybackSpeed(float speed) {
+ Bundle bundle = new Bundle();
+ bundle.putFloat(MediaSessionCompat.ACTION_ARGUMENT_PLAYBACK_SPEED, speed);
+ sendCustomAction(MediaSessionCompat.ACTION_SET_PLAYBACK_SPEED, bundle);
+ }
+
+ @Override
public void setCaptioningEnabled(boolean enabled) {
Bundle bundle = new Bundle();
bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
diff --git a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index 4b468c6..95eff3c 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -328,6 +328,15 @@
"android.support.v4.media.session.action.SET_RATING";
/**
+ * Custom action to invoke setPlaybackSpeed() with extra fields.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY)
+ public static final String ACTION_SET_PLAYBACK_SPEED =
+ "android.support.v4.media.session.action.SET_PLAYBACK_SPEED";
+
+ /**
* Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
*
* @hide
@@ -365,6 +374,15 @@
"android.support.v4.media.session.action.ARGUMENT_RATING";
/**
+ * Argument for use with {@link #ACTION_SET_PLAYBACK_SPEED} indicating the speed to be set.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY)
+ public static final String ACTION_ARGUMENT_PLAYBACK_SPEED =
+ "android.support.v4.media.session.action.ARGUMENT_PLAYBACK_SPEED";
+
+ /**
* Argument for use with various actions indicating extra bundle.
*
* @hide
@@ -1259,6 +1277,19 @@
}
/**
+ * Override to handle the playback speed change.
+ * To update the new playback speed, create a new {@link PlaybackStateCompat} by using
+ * {@link PlaybackStateCompat.Builder#setState(int, long, float)}, and set it with
+ * {@link #setPlaybackState(PlaybackStateCompat)}.
+ *
+ * @param speed the playback speed
+ * @see #setPlaybackState(PlaybackStateCompat)
+ * @see PlaybackStateCompat.Builder#setState(int, long, float)
+ */
+ public void onSetPlaybackSpeed(float speed) {
+ }
+
+ /**
* Override to handle requests to enable/disable captioning.
*
* @param enabled {@code true} to enable captioning, {@code false} to disable.
@@ -1579,6 +1610,9 @@
} else if (action.equals(ACTION_SET_RATING)) {
RatingCompat rating = extras.getParcelable(ACTION_ARGUMENT_RATING);
Callback.this.onSetRating(rating, bundle);
+ } else if (action.equals(ACTION_SET_PLAYBACK_SPEED)) {
+ float speed = extras.getFloat(ACTION_ARGUMENT_PLAYBACK_SPEED, 1.0f);
+ Callback.this.onSetPlaybackSpeed(speed);
} else {
Callback.this.onCustomAction(action, extras);
}
@@ -2947,6 +2981,11 @@
}
@Override
+ public void setPlaybackSpeed(float speed) throws RemoteException {
+ postToHandler(MessageHandler.MSG_SET_PLAYBACK_SPEED, speed);
+ }
+
+ @Override
public void setCaptioningEnabled(boolean enabled) throws RemoteException {
postToHandler(MessageHandler.MSG_SET_CAPTIONING_ENABLED, enabled);
}
@@ -3095,6 +3134,7 @@
}
class MessageHandler extends Handler {
+ // Next ID: 33
private static final int MSG_COMMAND = 1;
private static final int MSG_ADJUST_VOLUME = 2;
private static final int MSG_PREPARE = 3;
@@ -3115,6 +3155,7 @@
private static final int MSG_SEEK_TO = 18;
private static final int MSG_RATE = 19;
private static final int MSG_RATE_EXTRA = 31;
+ private static final int MSG_SET_PLAYBACK_SPEED = 32;
private static final int MSG_CUSTOM_ACTION = 20;
private static final int MSG_MEDIA_BUTTON = 21;
private static final int MSG_SET_VOLUME = 22;
@@ -3219,6 +3260,9 @@
case MSG_RATE_EXTRA:
cb.onSetRating((RatingCompat) msg.obj, extras);
break;
+ case MSG_SET_PLAYBACK_SPEED:
+ cb.onSetPlaybackSpeed((Float) msg.obj);
+ break;
case MSG_CUSTOM_ACTION:
cb.onCustomAction((String) msg.obj, extras);
break;
@@ -3911,6 +3955,12 @@
}
@Override
+ public void setPlaybackSpeed(float speed) throws RemoteException {
+ // Will not be called.
+ throw new AssertionError();
+ }
+
+ @Override
public void setCaptioningEnabled(boolean enabled) throws RemoteException {
// Will not be called.
throw new AssertionError();
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
index 1baba08..0f4d8e8 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -41,6 +41,7 @@
import static android.support.mediacompat.testlib.MediaControllerConstants
.SEND_CUSTOM_ACTION_PARCELABLE;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_PLAYBACK_SPEED;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_RATING;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_REPEAT_MODE;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_SHUFFLE_MODE;
@@ -231,6 +232,9 @@
case SET_SHUFFLE_MODE:
controls.setShuffleMode(extras.getInt(KEY_ARGUMENT));
break;
+ case SET_PLAYBACK_SPEED:
+ controls.setPlaybackSpeed(extras.getFloat(KEY_ARGUMENT));
+ break;
}
}
}
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
index 38ac323..56a99c2 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -37,6 +37,7 @@
import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_CUSTOM_ACTION;
import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_CUSTOM_ACTION_PARCELABLE;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_PLAYBACK_SPEED;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_RATING;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_REPEAT_MODE;
import static android.support.mediacompat.testlib.MediaControllerConstants.SET_SHUFFLE_MODE;
@@ -696,6 +697,26 @@
}
/**
+ * Tests {@link MediaSessionCompat.Callback#onSetPlaybackSpeed(float)}.
+ */
+ @Test
+ @SmallTest
+ public void testCallback_onSetPlaybackSpeed() {
+ if (!TextUtils.equals(VERSION_TOT, mClientVersion)) {
+ // In previous versions, MediaControllerCompat#setPlaybackSpeed() does not exist.
+ return;
+ }
+
+ mCallback.reset(1);
+ final float testSpeed = 2.0f;
+ callTransportControlsMethod(
+ SET_PLAYBACK_SPEED, testSpeed, getApplicationContext(), mSession.getSessionToken());
+ assertTrue(mCallback.await(TIME_OUT_MS));
+ assertTrue(mCallback.mOnSetPlaybackSpeedCalled);
+ assertEquals(testSpeed, mCallback.mSpeed, 0.0f);
+ }
+
+ /**
* Tests {@link MediaSessionCompat.Callback#onMediaButtonEvent}.
*/
@Test
@@ -1098,6 +1119,7 @@
private int mQueueIndex;
private MediaDescriptionCompat mQueueDescription;
private List<MediaSessionCompat.QueueItem> mQueue = new ArrayList<>();
+ private float mSpeed;
private int mOnPlayCalledCount;
private boolean mOnPauseCalled;
@@ -1124,6 +1146,7 @@
private boolean mOnAddQueueItemCalled;
private boolean mOnAddQueueItemAtCalled;
private boolean mOnRemoveQueueItemCalled;
+ private boolean mOnSetPlaybackSpeedCalled;
public void reset(int count) {
mLatch = new CountDownLatch(count);
@@ -1147,6 +1170,7 @@
mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
mQueueIndex = -1;
mQueueDescription = null;
+ mSpeed = -1.0f;
mRemoteUserInfoForStop = null;
mOnPlayCalledCount = 0;
@@ -1174,6 +1198,7 @@
mOnAddQueueItemCalled = false;
mOnAddQueueItemAtCalled = false;
mOnRemoveQueueItemCalled = false;
+ mOnSetPlaybackSpeedCalled = false;
}
public void reset(int count, String expectedCallerPackageName) {
@@ -1485,6 +1510,17 @@
mLatch.countDown();
}
+ @Override
+ public void onSetPlaybackSpeed(float speed) {
+ if (!isCallerTestClient()) {
+ // Ignore
+ return;
+ }
+ mOnSetPlaybackSpeedCalled = true;
+ mSpeed = speed;
+ mLatch.countDown();
+ }
+
private boolean isCallerTestClient() {
RemoteUserInfo info = mSession.getCurrentControllerInfo();
assertNotNull(info);
diff --git a/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java b/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
index 3041315..7bdd2fa 100644
--- a/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
+++ b/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -54,6 +54,7 @@
public static final int SET_CAPTIONING_ENABLED = 320;
public static final int SET_REPEAT_MODE = 321;
public static final int SET_SHUFFLE_MODE = 322;
+ public static final int SET_PLAYBACK_SPEED = 323;
private MediaControllerConstants() {
}
diff --git a/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java b/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java
index 14ba14c..532b2a7 100644
--- a/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java
+++ b/media/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -116,6 +116,8 @@
intent.putExtra(KEY_ARGUMENT, (long) arg);
} else if (arg instanceof Boolean) {
intent.putExtra(KEY_ARGUMENT, (boolean) arg);
+ } else if (arg instanceof Float) {
+ intent.putExtra(KEY_ARGUMENT, (float) arg);
} else if (arg instanceof Parcelable) {
intent.putExtra(KEY_ARGUMENT, (Parcelable) arg);
} else if (arg instanceof ArrayList<?>) {
diff --git a/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java b/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java
index 855835b..d88a058 100644
--- a/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java
@@ -513,12 +513,6 @@
}
@Override
- public ListenableFuture<SessionResult> setPlaybackSpeed(float speed) {
- // Unsupported action
- return createFutureWithResult(RESULT_ERROR_NOT_SUPPORTED);
- }
-
- @Override
public @BuffState int getBufferingState() {
synchronized (mLock) {
if (!mConnected) {
@@ -571,6 +565,18 @@
}
@Override
+ public ListenableFuture<SessionResult> setPlaybackSpeed(float speed) {
+ synchronized (mLock) {
+ if (!mConnected) {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ return createFutureWithResult(RESULT_ERROR_SESSION_DISCONNECTED);
+ }
+ mControllerCompat.getTransportControls().setPlaybackSpeed(speed);
+ }
+ return createFutureWithResult(RESULT_SUCCESS);
+ }
+
+ @Override
public ListenableFuture<SessionResult> sendCustomCommand(@NonNull SessionCommand command,
@Nullable Bundle args) {
synchronized (mLock) {
diff --git a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
index 42be686..f1a0b4b 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
@@ -299,6 +299,16 @@
}
@Override
+ public void onSetPlaybackSpeed(final float speed) {
+ dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED, new SessionTask() {
+ @Override
+ public void run(ControllerInfo controller) throws RemoteException {
+ mSessionImpl.setPlaybackSpeed(speed);
+ }
+ });
+ }
+
+ @Override
public void onSkipToQueueItem(final long queueId) {
dispatchSessionTask(SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PLAYLIST_ITEM,
new SessionTask() {
diff --git a/media2/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl b/media2/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl
index 53a2e99..335fc00 100644
--- a/media2/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl
+++ b/media2/version-compat-tests/common/src/main/aidl/androidx/media2/test/common/IRemoteMediaControllerCompat.aidl
@@ -44,6 +44,7 @@
void pause(String controllerId);
void stop(String controllerId);
void seekTo(String controllerId, long pos);
+ void setPlaybackSpeed(String controllerId, float speed);
void fastForward(String controllerId);
void skipToNext(String controllerId);
void rewind(String controllerId);
diff --git a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
index 213ca3b..3100d5c 100644
--- a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
+++ b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/MediaControllerCompatProviderService.java
@@ -240,6 +240,12 @@
}
@Override
+ public void setPlaybackSpeed(String controllerId, float speed) throws RemoteException {
+ MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
+ controller.getTransportControls().setPlaybackSpeed(speed);
+ }
+
+ @Override
public void fastForward(String controllerId) throws RemoteException {
MediaControllerCompat controller = mMediaControllerCompatMap.get(controllerId);
controller.getTransportControls().fastForward();
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
index faf93f4..d13e523 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
@@ -248,6 +248,14 @@
}
}
+ public void setPlaybackSpeed(float speed) {
+ try {
+ mBinder.setPlaybackSpeed(mControllerId, speed);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to call setPlaybackSpeed()");
+ }
+ }
+
public void fastForward() {
try {
mBinder.fastForward(mControllerId);
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionLegacyCallbackTest.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
similarity index 97%
rename from media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionLegacyCallbackTest.java
rename to media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
index 8b93d02..912c37d 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionLegacyCallbackTest.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
@@ -79,8 +79,8 @@
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
-public class MediaSessionLegacyCallbackTest extends MediaSessionTestBase {
- private static final String TAG = "MediaSessionLegacyCallbackTest";
+public class MediaSessionCallbackTestWithMediaControllerCompat extends MediaSessionTestBase {
+ private static final String TAG = "MediaSessionCallbackTestWithMediaControllerCompat";
private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
(Build.VERSION.SDK_INT < 21 || Build.VERSION.SDK_INT >= 24)
@@ -214,6 +214,20 @@
}
@Test
+ public void testSetPlaybackSpeed() {
+ prepareLooper();
+ final float testSpeed = 2.0f;
+ mController.getTransportControls().setPlaybackSpeed(testSpeed);
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mSetPlaybackSpeedCalled);
+ assertEquals(testSpeed, mPlayer.mPlaybackSpeed, 0.0f);
+ }
+
+ @Test
public void testAddQueueItem() throws InterruptedException {
prepareLooper();
final int playlistSize = 10;
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java
index 758a680..59349bf 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java
@@ -173,6 +173,19 @@
}
@Test
+ public void testSetPlaybackSpeed() throws Exception {
+ prepareLooper();
+ RemoteMediaController controller = createControllerAndWaitConnection();
+ mSessionCallback.reset(1);
+
+ final float testSpeed = 2.0f;
+ controller.setPlaybackSpeed(testSpeed);
+ assertTrue(mSessionCallback.await(TIME_OUT_MS));
+ assertTrue(mSessionCallback.mOnSetPlaybackSpeedCalled);
+ assertEquals(testSpeed, mSessionCallback.mSpeed, 0.0f);
+ }
+
+ @Test
public void testAddPlaylistItem() throws Exception {
prepareLooper();
final List<MediaItem> testList = MediaTestUtils.createPlaylist(2);
@@ -612,6 +625,7 @@
private class MediaSessionCallback extends MediaSessionCompat.Callback {
private CountDownLatch mLatch = new CountDownLatch(1);
private long mSeekPosition;
+ private float mSpeed;
private long mQueueItemId;
private RatingCompat mRating;
private String mMediaId;
@@ -636,6 +650,7 @@
private boolean mOnSkipToPreviousCalled;
private boolean mOnSkipToNextCalled;
private boolean mOnSeekToCalled;
+ private boolean mOnSetPlaybackSpeedCalled;
private boolean mOnSkipToQueueItemCalled;
private boolean mOnSetRatingCalled;
private boolean mOnPlayFromMediaIdCalled;
@@ -657,6 +672,7 @@
public void reset(int count) {
mLatch = new CountDownLatch(count);
mSeekPosition = -1;
+ mSpeed = -1.0f;
mQueueItemId = -1;
mRating = null;
mMediaId = null;
@@ -682,6 +698,7 @@
mOnSkipToNextCalled = false;
mOnSkipToQueueItemCalled = false;
mOnSeekToCalled = false;
+ mOnSetPlaybackSpeedCalled = false;
mOnSetRatingCalled = false;
mOnPlayFromMediaIdCalled = false;
mOnPlayFromSearchCalled = false;
@@ -761,6 +778,13 @@
}
@Override
+ public void onSetPlaybackSpeed(float speed) {
+ mOnSetPlaybackSpeedCalled = true;
+ mSpeed = speed;
+ mLatch.countDown();
+ }
+
+ @Override
public void onSetRating(RatingCompat rating) {
mOnSetRatingCalled = true;
mRating = rating;
diff --git a/preference/api/1.1.0-alpha04.txt b/preference/api/1.1.0-alpha04.txt
index d3b0755..0edd4f7 100644
--- a/preference/api/1.1.0-alpha04.txt
+++ b/preference/api/1.1.0-alpha04.txt
@@ -481,12 +481,16 @@
method public int getMax();
method public int getMin();
method public final int getSeekBarIncrement();
+ method public boolean getShowSeekBarValue();
+ method public boolean getUpdatesContinuously();
method public int getValue();
method public boolean isAdjustable();
method public void setAdjustable(boolean);
method public final void setMax(int);
method public void setMin(int);
method public final void setSeekBarIncrement(int);
+ method public void setShowSeekBarValue(boolean);
+ method public void setUpdatesContinuously(boolean);
method public void setValue(int);
}
diff --git a/preference/api/current.txt b/preference/api/current.txt
index d3b0755..0edd4f7 100644
--- a/preference/api/current.txt
+++ b/preference/api/current.txt
@@ -481,12 +481,16 @@
method public int getMax();
method public int getMin();
method public final int getSeekBarIncrement();
+ method public boolean getShowSeekBarValue();
+ method public boolean getUpdatesContinuously();
method public int getValue();
method public boolean isAdjustable();
method public void setAdjustable(boolean);
method public final void setMax(int);
method public void setMin(int);
method public final void setSeekBarIncrement(int);
+ method public void setShowSeekBarValue(boolean);
+ method public void setUpdatesContinuously(boolean);
method public void setValue(int);
}
diff --git a/preference/res/values/attrs.xml b/preference/res/values/attrs.xml
index 8b1d83c..50aac79 100644
--- a/preference/res/values/attrs.xml
+++ b/preference/res/values/attrs.xml
@@ -324,6 +324,11 @@
value will be displayed. If true, the view is VISIBLE; if false, the view will be GONE.
By default, this view is GONE. -->
<attr name="showSeekBarValue" format="boolean" />
+ <!-- Flag indicating whether the SeekBarPreference should continuously save the Seekbar
+ value while the Seekbar is being dragged. If true, the SeekBarPreference should continuously
+ save the Seekbar value while it is being dragged. If false, the Seekbar value is only saved
+ when released. By default, this boolean is false. -->
+ <attr name="updatesContinuously" format="boolean" />
</declare-styleable>
<declare-styleable name="SwitchPreference">
diff --git a/preference/res/values/styles.xml b/preference/res/values/styles.xml
index 3eaada8..001e097 100644
--- a/preference/res/values/styles.xml
+++ b/preference/res/values/styles.xml
@@ -55,6 +55,7 @@
<item name="android:layout">@layout/preference_widget_seekbar</item>
<item name="adjustable">true</item>
<item name="showSeekBarValue">true</item>
+ <item name="updatesContinuously">false</item>
</style>
<style name="Preference.PreferenceScreen">
diff --git a/preference/src/main/java/androidx/preference/SeekBarPreference.java b/preference/src/main/java/androidx/preference/SeekBarPreference.java
index 920fdd7..6be1b40 100644
--- a/preference/src/main/java/androidx/preference/SeekBarPreference.java
+++ b/preference/src/main/java/androidx/preference/SeekBarPreference.java
@@ -64,13 +64,17 @@
boolean mAdjustable;
// Whether to show the SeekBar value TextView next to the bar
private boolean mShowSeekBarValue;
+ // Whether the SeekBarPreference should continuously save the Seekbar value while it is being
+ // dragged.
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ boolean mUpdatesContinuously;
/**
* Listener reacting to the {@link SeekBar} changing value by the user
*/
private OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (fromUser && !mTrackingTouch) {
+ if (fromUser && (mUpdatesContinuously || !mTrackingTouch)) {
syncValueInternal(seekBar);
}
}
@@ -136,6 +140,8 @@
setSeekBarIncrement(a.getInt(R.styleable.SeekBarPreference_seekBarIncrement, 0));
mAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true);
mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, false);
+ mUpdatesContinuously = a.getBoolean(R.styleable.SeekBarPreference_updatesContinuously,
+ false);
a.recycle();
}
@@ -291,6 +297,56 @@
mAdjustable = adjustable;
}
+ /**
+ * Gets whether the {@link SeekBarPreference} should continuously save the {@link SeekBar} value
+ * while it is being dragged. Note that when the value is true,
+ * {@link Preference#OnPreferenceChangeListener} will be called continuously as well.
+ *
+ * {@see #setUpdatesContinuously()}
+ *
+ * @return Whether the {@link SeekBarPreference} should continuously save the {@link SeekBar}
+ * value while it is being dragged
+ */
+ public boolean getUpdatesContinuously() {
+ return mUpdatesContinuously;
+ }
+
+ /**
+ * Sets whether the {@link SeekBarPreference} should continuously save the {@link SeekBar} value
+ * while it is being dragged.
+ *
+ * {@see #getUpdatesContinuously()}
+ *
+ * @param updatesContinuously Whether the {@link SeekBarPreference} should continuously save
+ * the {@link SeekBar} value while it is being dragged
+ */
+ public void setUpdatesContinuously(boolean updatesContinuously) {
+ mUpdatesContinuously = updatesContinuously;
+ }
+
+ /**
+ * Gets whether the current {@link SeekBar} value is displayed to the user.
+ *
+ * {@see #setShowSeekBarValue()}
+ *
+ * @return Whether the current {@link SeekBar} value is displayed to the user
+ */
+ public boolean getShowSeekBarValue() {
+ return mShowSeekBarValue;
+ }
+
+ /**
+ * Sets whether the current {@link SeekBar} value is displayed to the user.
+ *
+ * {@see #getShowSeekBarValue()}
+ *
+ * @param showSeekBarValue Whether the current {@link SeekBar} value is displayed to the user
+ */
+ public void setShowSeekBarValue(boolean showSeekBarValue) {
+ mShowSeekBarValue = showSeekBarValue;
+ notifyChanged();
+ }
+
private void setValueInternal(int seekBarValue, boolean notifyChanged) {
if (seekBarValue < mMin) {
seekBarValue = mMin;
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index 4d15cf6..4c74d26 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -51,7 +51,7 @@
compile(JAVAPOET)
compile(ANTLR)
compile(XERIAL)
- compile(KOTLIN_METADATA)
+ compile(KOTLIN_METADATA_JVM)
compile(APACHE_COMMONS_CODEC)
testCompile(GOOGLE_COMPILE_TESTING)
testCompile project(":paging:paging-common")
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/KotlinMetadataElement.kt b/room/compiler/src/main/kotlin/androidx/room/ext/KotlinMetadataElement.kt
deleted file mode 100644
index 8eae79d..0000000
--- a/room/compiler/src/main/kotlin/androidx/room/ext/KotlinMetadataElement.kt
+++ /dev/null
@@ -1,122 +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.room.ext
-
-import androidx.room.processor.Context
-import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
-import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
-import me.eugeniomarletti.kotlin.metadata.isPrimary
-import me.eugeniomarletti.kotlin.metadata.isSuspend
-import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
-import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
-import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf
-import me.eugeniomarletti.kotlin.metadata.shadow.serialization.deserialization.getName
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Element
-import javax.lang.model.element.ExecutableElement
-
-/**
- * Utility class for processors that wants to run kotlin specific code.
- */
-class KotlinMetadataElement private constructor(
- val context: Context,
- val element: Element,
- private val classMetadata: KotlinClassMetadata
-) : KotlinMetadataUtils {
-
- override val processingEnv: ProcessingEnvironment
- get() = context.processingEnv
-
- /**
- * Returns the parameter names of the function or constructor if all have names embedded in the
- * metadata.
- */
- fun getParameterNames(method: ExecutableElement): List<String>? {
- val valueParameterList = classMetadata.data.getFunctionOrNull(method)?.valueParameterList
- ?: findConstructor(method)?.valueParameterList
- ?: return null
- return if (valueParameterList.all { it.hasName() }) {
- valueParameterList.map {
- classMetadata.data.nameResolver.getName(it.name)
- .asString()
- .replace("`", "")
- .removeSuffix("?")
- .trim()
- }
- } else {
- null
- }
- }
-
- /**
- * Finds the kotlin metadata for a constructor.
- */
- private fun findConstructor(
- executableElement: ExecutableElement
- ): ProtoBuf.Constructor? = classMetadata?.let { metadata ->
- val (nameResolver, classProto) = metadata.data
- val jvmSignature = executableElement.jvmMethodSignature
- // find constructor
- return classProto.constructorList.singleOrNull {
- it.getJvmConstructorSignature(nameResolver, classProto.typeTable) == jvmSignature
- }
- }
-
- /**
- * Finds the primary constructor signature of the class.
- */
- fun findPrimaryConstructorSignature() = classMetadata.data.let { data ->
- data.classProto
- .constructorList.first { it.isPrimary }
- .getJvmConstructorSignature(
- data.nameResolver,
- data.classProto.typeTable
- )
- }
-
- fun getMethodSignature(executableElement: ExecutableElement) =
- executableElement.jvmMethodSignature
-
- /**
- * Checks if a method is a suspend function.
- */
- fun isSuspendFunction(method: ExecutableElement) =
- classMetadata.data.getFunctionOrNull(method)?.isSuspend == true
-
- companion object {
-
- /**
- * Creates a [KotlinMetadataElement] for the given element if it contains Kotlin metadata,
- * otherwise this method returns null.
- *
- * Usually the [element] passed must represent a class. For example, if kotlin metadata is
- * desired for a method, then the containing method should be used as parameter.
- */
- fun createFor(context: Context, element: Element): KotlinMetadataElement? {
- val metadata = try {
- element.kotlinMetadata
- } catch (throwable: Throwable) {
- context.logger.d(element, "failed to read get kotlin metadata from %s", element)
- } as? KotlinClassMetadata
- return if (metadata != null) {
- KotlinMetadataElement(context, element, metadata)
- } else {
- null
- }
- }
- }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
index 05cbde9..f7809d5 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
@@ -22,7 +22,6 @@
import com.google.auto.common.AnnotationMirrors
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
-import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAbi
import java.lang.reflect.Proxy
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.AnnotationMirror
@@ -342,6 +341,11 @@
}
/**
+ * Suffix of the Kotlin synthetic class created interface method implementations.
+ */
+const val DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"
+
+/**
* Finds the default implementation method corresponding to this Kotlin interface method.
*/
fun Element.findKotlinDefaultImpl(typeUtils: Types): Element? {
@@ -360,7 +364,7 @@
val parent = this.enclosingElement as TypeElement
val innerClass = parent.enclosedElements.find {
- it.kind == ElementKind.CLASS && it.simpleName.contentEquals(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)
+ it.kind == ElementKind.CLASS && it.simpleName.contentEquals(DEFAULT_IMPLS_CLASS_NAME)
} ?: return null
return innerClass.enclosedElements.find {
it.kind == ElementKind.METHOD && it.simpleName == this.simpleName &&
diff --git a/room/compiler/src/main/kotlin/androidx/room/kotlin/JvmDescriptorUtils.kt b/room/compiler/src/main/kotlin/androidx/room/kotlin/JvmDescriptorUtils.kt
new file mode 100644
index 0000000..8e043d1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/kotlin/JvmDescriptorUtils.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.room.kotlin
+
+import com.google.auto.common.MoreElements
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.NestingKind
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.NullType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.AbstractTypeVisitor6
+import javax.lang.model.util.Types
+
+/**
+ * Returns the method descriptor of this [ExecutableElement].
+ *
+ * For reference, see the [JVM specification, section 4.3.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3)
+ */
+fun ExecutableElement.descriptor(typeUtils: Types) =
+ "$simpleName${asType().descriptor(typeUtils)}"
+
+/**
+ * Returns the name of this [TypeElement] in its "internal form".
+ *
+ * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+ */
+internal val TypeElement.internalName: String
+ get() = when (nestingKind) {
+ NestingKind.TOP_LEVEL ->
+ qualifiedName.toString().replace('.', '/')
+ NestingKind.MEMBER ->
+ MoreElements.asType(enclosingElement).internalName + "$" + simpleName
+ NestingKind.LOCAL, NestingKind.ANONYMOUS ->
+ error("Unsupported nesting $nestingKind")
+ }
+
+internal val NoType.descriptor: String
+ get() = "V"
+
+internal val DeclaredType.descriptor: String
+ get() = "L" + MoreElements.asType(asElement()).internalName + ";"
+
+internal val PrimitiveType.descriptor: String
+ get() = when (this.kind) {
+ TypeKind.BYTE -> "B"
+ TypeKind.CHAR -> "C"
+ TypeKind.DOUBLE -> "D"
+ TypeKind.FLOAT -> "F"
+ TypeKind.INT -> "I"
+ TypeKind.LONG -> "J"
+ TypeKind.SHORT -> "S"
+ TypeKind.BOOLEAN -> "Z"
+ else -> error("Unknown primitive type $this")
+ }
+
+fun TypeMirror.descriptor(typeUtils: Types): String =
+ accept(JvmDescriptorTypeVisitor, typeUtils)
+
+internal fun WildcardType.descriptor(typeUtils: Types): String =
+ typeUtils.erasure(this).descriptor(typeUtils)
+
+internal fun TypeVariable.descriptor(typeUtils: Types): String =
+ typeUtils.erasure(this).descriptor(typeUtils)
+
+internal fun ArrayType.descriptor(typeUtils: Types): String =
+ "[" + componentType.descriptor(typeUtils)
+
+internal fun ExecutableType.descriptor(typeUtils: Types): String {
+ val parameterDescriptors =
+ parameterTypes.joinToString(separator = "") { it.descriptor(typeUtils) }
+ val returnDescriptor = returnType.descriptor(typeUtils)
+ return "($parameterDescriptors)$returnDescriptor"
+}
+
+/**
+ * When applied over a type, it returns either:
+ * + a "field descriptor", for example: `Ljava/lang/Object;`
+ * + a "method descriptor", for example: `(Ljava/lang/Object;)Z`
+ *
+ * The easiest way to use this is through [TypeMirror.descriptor][JvmDescriptorUtils.descriptor] in [JvmDescriptorUtils].
+ *
+ * For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor6<String, Types>() {
+ override fun visitNoType(t: NoType, typeUtils: Types): String = t.descriptor
+
+ override fun visitDeclared(t: DeclaredType, typeUtils: Types): String = t.descriptor
+
+ override fun visitPrimitive(t: PrimitiveType, typeUtils: Types): String = t.descriptor
+
+ override fun visitArray(t: ArrayType, typeUtils: Types): String = t.descriptor(typeUtils)
+
+ override fun visitWildcard(t: WildcardType, typeUtils: Types): String = t.descriptor(typeUtils)
+
+ override fun visitExecutable(t: ExecutableType, typeUtils: Types): String =
+ t.descriptor(typeUtils)
+
+ override fun visitTypeVariable(t: TypeVariable, typeUtils: Types): String =
+ t.descriptor(typeUtils)
+
+ override fun visitNull(t: NullType, typeUtils: Types): String =
+ visitUnknown(t, typeUtils)
+
+ override fun visitError(t: ErrorType, typeUtils: Types): String =
+ visitUnknown(t, typeUtils)
+
+ override fun visitUnknown(t: TypeMirror, typeUtils: Types): String =
+ error("Unsupported type $t")
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/kotlin/KotlinClassMetadataUtils.kt b/room/compiler/src/main/kotlin/androidx/room/kotlin/KotlinClassMetadataUtils.kt
new file mode 100644
index 0000000..9a7effe
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/kotlin/KotlinClassMetadataUtils.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.room.kotlin
+
+import kotlinx.metadata.Flag
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClassVisitor
+import kotlinx.metadata.KmConstructorExtensionVisitor
+import kotlinx.metadata.KmConstructorVisitor
+import kotlinx.metadata.KmExtensionType
+import kotlinx.metadata.KmFunctionExtensionVisitor
+import kotlinx.metadata.KmFunctionVisitor
+import kotlinx.metadata.KmValueParameterVisitor
+import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+
+/**
+ * Represents the kotlin metadata of a function
+ */
+data class KmFunction(
+ val descriptor: String,
+ private val flags: Flags,
+ val parameters: List<KmValueParameter>
+) {
+ fun isSuspend() = Flag.Function.IS_SUSPEND(flags)
+}
+
+/**
+ * Represents the kotlin metadata of a constructor
+ */
+data class KmConstructor(
+ val descriptor: String,
+ private val flags: Flags,
+ val parameters: List<KmValueParameter>
+) {
+ fun isPrimary() = Flag.Constructor.IS_PRIMARY(flags)
+}
+
+/**
+ * Represents the kotlin metadata of a parameter
+ */
+data class KmValueParameter(val name: String, private val flags: Flags)
+
+internal fun KotlinClassMetadata.Class.readFunctions(): List<KmFunction> =
+ mutableListOf<KmFunction>().apply { accept(FunctionReader(this)) }
+
+private class FunctionReader(val result: MutableList<KmFunction>) : KmClassVisitor() {
+ override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? {
+ return object : KmFunctionVisitor() {
+
+ lateinit var descriptor: String
+ val parameters = mutableListOf<KmValueParameter>()
+
+ override fun visitValueParameter(
+ flags: Flags,
+ name: String
+ ): KmValueParameterVisitor? {
+ parameters.add(KmValueParameter(name, flags))
+ return super.visitValueParameter(flags, name)
+ }
+
+ override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? {
+ if (type != JvmFunctionExtensionVisitor.TYPE) {
+ error("Unsupported extension type: $type")
+ }
+ return object : JvmFunctionExtensionVisitor() {
+ override fun visit(desc: JvmMethodSignature?) {
+ descriptor = desc!!.asString()
+ }
+ }
+ }
+
+ override fun visitEnd() {
+ result.add(KmFunction(descriptor, flags, parameters))
+ }
+ }
+ }
+}
+
+internal fun KotlinClassMetadata.Class.readConstructors(): List<KmConstructor> =
+ mutableListOf<KmConstructor>().apply { accept(ConstructorReader(this)) }
+
+private class ConstructorReader(val result: MutableList<KmConstructor>) : KmClassVisitor() {
+ override fun visitConstructor(flags: Flags): KmConstructorVisitor? {
+ return object : KmConstructorVisitor() {
+
+ lateinit var descriptor: String
+ val parameters = mutableListOf<KmValueParameter>()
+
+ override fun visitValueParameter(
+ flags: Flags,
+ name: String
+ ): KmValueParameterVisitor? {
+ parameters.add(KmValueParameter(name, flags))
+ return super.visitValueParameter(flags, name)
+ }
+
+ override fun visitExtensions(type: KmExtensionType): KmConstructorExtensionVisitor? {
+ if (type != JvmConstructorExtensionVisitor.TYPE) {
+ error("Unsupported extension type: $type")
+ }
+ return object : JvmConstructorExtensionVisitor() {
+ override fun visit(desc: JvmMethodSignature?) {
+ descriptor = desc!!.asString()
+ }
+ }
+ }
+
+ override fun visitEnd() {
+ result.add(KmConstructor(descriptor, flags, parameters))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/kotlin/KotlinMetadataElement.kt b/room/compiler/src/main/kotlin/androidx/room/kotlin/KotlinMetadataElement.kt
new file mode 100644
index 0000000..1db7d37
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/kotlin/KotlinMetadataElement.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.room.kotlin
+
+import androidx.room.processor.Context
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * Utility class for processors that wants to run kotlin specific code.
+ */
+class KotlinMetadataElement(
+ val context: Context,
+ val element: Element,
+ private val classMetadata: KotlinClassMetadata.Class
+) {
+
+ private val functionList: List<KmFunction> by lazy { classMetadata.readFunctions() }
+ private val constructorList: List<KmConstructor> by lazy { classMetadata.readConstructors() }
+
+ private val ExecutableElement.descriptor: String
+ get() = descriptor(context.processingEnv.typeUtils)
+
+ /**
+ * Returns the parameter names of the function or constructor if all have names embedded in the
+ * metadata.
+ */
+ fun getParameterNames(method: ExecutableElement): List<String>? {
+ val methodSignature = method.descriptor
+ val paramList =
+ functionList.firstOrNull { it.descriptor == methodSignature }?.parameters
+ ?: constructorList.firstOrNull { it.descriptor == methodSignature }?.parameters
+ return paramList?.map { it.name }
+ }
+
+ /**
+ * Finds the primary constructor descriptor of the class.
+ */
+ fun findPrimaryConstructorSignature() = constructorList.first { it.isPrimary() }.descriptor
+
+ /**
+ * Checks if a method is a suspend function.
+ */
+ fun isSuspendFunction(method: ExecutableElement) = functionList.firstOrNull {
+ it.descriptor == method.descriptor
+ }?.isSuspend() ?: false
+
+ companion object {
+
+ /**
+ * Creates a [KotlinMetadataElement] for the given element if it contains Kotlin metadata,
+ * otherwise this method returns null.
+ *
+ * Usually the [element] passed must represent a class. For example, if kotlin metadata is
+ * desired for a method, then the containing method should be used as parameter.
+ */
+ fun createFor(context: Context, element: Element): KotlinMetadataElement? {
+ val metadata = getMetadataAnnotation(element)?.run {
+ KotlinClassHeader(
+ kind = kind,
+ metadataVersion = metadataVersion,
+ bytecodeVersion = bytecodeVersion,
+ data1 = data1,
+ data2 = data2,
+ extraString = extraString,
+ packageName = packageName,
+ extraInt = extraInt
+ ).let {
+ // TODO: Support more metadata kind (file facade, synthetic class, etc...)
+ KotlinClassMetadata.read(it) as? KotlinClassMetadata.Class
+ }
+ }
+ return if (metadata != null) {
+ KotlinMetadataElement(context, element, metadata)
+ } else {
+ context.logger.d(
+ element, "Failed to read get kotlin metadata for %s", element)
+ null
+ }
+ }
+
+ /**
+ * Search for Kotlin's Metadata annotation across the element's hierarchy.
+ */
+ private fun getMetadataAnnotation(element: Element?): Metadata? =
+ if (element != null) {
+ element.getAnnotation(Metadata::class.java)
+ ?: getMetadataAnnotation(element.enclosingElement)
+ } else {
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index cbadf55..2098ade 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -16,13 +16,13 @@
package androidx.room.processor
-import androidx.room.ext.KotlinMetadataElement
import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.L
import androidx.room.ext.N
import androidx.room.ext.RoomCoroutinesTypeNames
import androidx.room.ext.T
import androidx.room.ext.getSuspendFunctionReturnType
+import androidx.room.kotlin.KotlinMetadataElement
import androidx.room.parser.ParsedQuery
import androidx.room.solver.prepared.binder.CallablePreparedQueryResultBinder.Companion.createPreparedBinder
import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index 45ebe64..3f4fea9 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -20,7 +20,6 @@
import androidx.room.Embedded
import androidx.room.Ignore
import androidx.room.Relation
-import androidx.room.ext.KotlinMetadataElement
import androidx.room.ext.extendsBoundOrSelf
import androidx.room.ext.getAllFieldsIncludingPrivateSupers
import androidx.room.ext.hasAnnotation
@@ -29,6 +28,8 @@
import androidx.room.ext.isCollection
import androidx.room.ext.toAnnotationBox
import androidx.room.ext.typeName
+import androidx.room.kotlin.KotlinMetadataElement
+import androidx.room.kotlin.descriptor
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
@@ -375,7 +376,7 @@
val primaryConstructor =
kotlinMetadata?.findPrimaryConstructorSignature()?.let { signature ->
goodConstructors.firstOrNull {
- kotlinMetadata.getMethodSignature(it.element) == signature
+ it.element.descriptor(context.processingEnv.typeUtils) == signature
}
}
if (primaryConstructor != null) {
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index e5bf595..8b95edb 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -16,6 +16,7 @@
package androidx.room.writer
+import androidx.room.ext.DEFAULT_IMPLS_CLASS_NAME
import androidx.room.ext.L
import androidx.room.ext.N
import androidx.room.ext.RoomTypeNames
@@ -40,7 +41,6 @@
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
-import me.eugeniomarletti.kotlin.metadata.shadow.load.java.JvmAbi
import stripNonJava
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.ElementKind
@@ -217,7 +217,7 @@
TransactionMethod.CallType.DEFAULT_KOTLIN -> {
append("$N.$N.$N(this")
params.add(element.enclosingElement.simpleName)
- params.add(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)
+ params.add(DEFAULT_IMPLS_CLASS_NAME)
params.add(element.simpleName)
}
}
diff --git a/room/compiler/src/test/kotlin/androidx/room/kotlin/JvmDescriptorUtilsTest.kt b/room/compiler/src/test/kotlin/androidx/room/kotlin/JvmDescriptorUtilsTest.kt
new file mode 100644
index 0000000..f5f1052
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/kotlin/JvmDescriptorUtilsTest.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.room.kotlin
+
+import androidx.room.testing.TestProcessor
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import toJFO
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class JvmDescriptorUtilsTest {
+
+ private val describeAnnotation =
+ """
+ package androidx.room.test;
+
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+
+ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ public @interface Describe { }
+ """.toJFO("androidx.room.test.Describe")
+
+ @Test
+ fun descriptor_method_simple() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ public class DummyClass {
+ @Describe
+ public void emptyMethod() {
+ }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors.first())
+ .isEqualTo("emptyMethod()V")
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_primitiveParams() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(boolean yesOrNo, int number) { }
+
+ @Describe
+ byte method2(char letter) { return 0; }
+
+ @Describe
+ void method3(double realNumber1, float realNummber2) { }
+
+ @Describe
+ void method4(long bigNumber, short littlerNumber) { }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors)
+ .isEqualTo(setOf("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V"))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_classParam_javaTypes() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ import java.util.ArrayList;
+ import java.util.List;
+
+ class DummyClass {
+ @Describe
+ void method1(Object something) { }
+
+ @Describe
+ Object method2() { return null; }
+
+ @Describe
+ List<String> method3(ArrayList<Integer> list) { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Ljava/lang/Object;)V",
+ "method2()Ljava/lang/Object;",
+ "method3(Ljava/util/ArrayList;)Ljava/util/List;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_classParam_testClass() {
+ val extraJfo =
+ """
+ package androidx.room.test;
+
+ class DataClass { }
+ """.toJFO("androidx.room.test.DataClass")
+
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(DataClass data) { }
+
+ @Describe
+ DataClass method2() { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass"), extraJfo
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Landroidx/room/test/DataClass;)V",
+ "method2()Landroidx/room/test/DataClass;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_classParam_innerTestClass() {
+ val extraJfo =
+ """
+ package androidx.room.test;
+
+ class DataClass {
+
+ class MemberInnerData { }
+
+ static class StaticInnerData { }
+
+ enum EnumData {
+ VALUE1, VALUE2
+ }
+ }
+ """.toJFO("androidx.room.test.DataClass")
+
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(DataClass.MemberInnerData data) { }
+
+ @Describe
+ void method2(DataClass.StaticInnerData data) { }
+
+ @Describe
+ void method3(DataClass.EnumData enumData) { }
+
+ @Describe
+ DataClass.StaticInnerData method4() { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass"), extraJfo
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Landroidx/room/test/DataClass\$MemberInnerData;)V",
+ "method2(Landroidx/room/test/DataClass\$StaticInnerData;)V",
+ "method3(Landroidx/room/test/DataClass\$EnumData;)V",
+ "method4()Landroidx/room/test/DataClass\$StaticInnerData;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_arrayParams() {
+ val extraJfo =
+ """
+ package androidx.room.test;
+
+ class DataClass { }
+ """.toJFO("androidx.room.test.DataClass")
+
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(DataClass[] data) { }
+
+ @Describe
+ DataClass[] method2() { return null; }
+
+ @Describe
+ void method3(int[] array) { }
+
+ @Describe
+ void method4(int... array) { }
+ }
+ """.toJFO("androidx.room.test.DummyClass"), extraJfo
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1([Landroidx/room/test/DataClass;)V",
+ "method2()[Landroidx/room/test/DataClass;",
+ "method3([I)V",
+ "method4([I)V"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ private fun singleRun(
+ vararg jfo: JavaFileObject,
+ handler: (Set<String>) -> Unit
+ ): CompileTester = Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ .that(listOf(describeAnnotation) + jfo)
+ .processedWith(TestProcessor.builder()
+ .nextRunHandler {
+ it.roundEnv.getElementsAnnotatedWith(it.annotations.first()).map { element ->
+ MoreElements.asExecutable(element).descriptor(it.processingEnv.typeUtils)
+ }.toSet().let(handler)
+ true
+ }
+ .forAnnotations("androidx.room.test.Describe")
+ .build())
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/androidx/room/kotlin/KotlinMetadataElementTest.kt b/room/compiler/src/test/kotlin/androidx/room/kotlin/KotlinMetadataElementTest.kt
new file mode 100644
index 0000000..a10de87
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/kotlin/KotlinMetadataElementTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.room.kotlin
+
+import androidx.room.processor.Context
+import androidx.room.testing.TestInvocation
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+import javax.lang.model.util.ElementFilter
+
+@RunWith(JUnit4::class)
+class KotlinMetadataElementTest {
+
+ @Test
+ fun getParameterNames() {
+ simpleRun { invocation ->
+ val (testClassElement, metadataElement) = getMetadataElement(invocation)
+ assertThat(ElementFilter.methodsIn(testClassElement.enclosedElements)
+ .first { it.simpleName.toString() == "functionWithParams" }
+ .let { metadataElement.getParameterNames(MoreElements.asExecutable(it)) }
+ ).isEqualTo(
+ listOf("param1", "yesOrNo", "number")
+ )
+ }
+ }
+
+ @Test
+ fun findPrimaryConstructorSignature() {
+ simpleRun { invocation ->
+ val (testClassElement, metadataElement) = getMetadataElement(invocation)
+ assertThat(
+ ElementFilter.constructorsIn(testClassElement.enclosedElements).map {
+ val desc = MoreElements.asExecutable(it).descriptor(invocation.typeUtils)
+ desc to (desc == metadataElement.findPrimaryConstructorSignature())
+ }.toSet()
+ ).isEqualTo(
+ setOf(
+ "TestData(Ljava/lang/String;)Landroidx/room/kotlin/" +
+ "KotlinMetadataElementTest\$TestData" to true,
+ "TestData(Landroidx/room/kotlin/KotlinMetadataElementTest\$TestData" to false
+ )
+ )
+ }
+ }
+
+ @Test
+ fun isSuspendFunction() {
+ simpleRun { invocation ->
+ val (testClassElement, metadataElement) = getMetadataElement(invocation)
+ assertThat(ElementFilter.constructorsIn(testClassElement.enclosedElements).map {
+ val executableElement = MoreElements.asExecutable(it)
+ executableElement.simpleName.toString() to metadataElement.isSuspendFunction(
+ executableElement
+ )
+ }.toSet()).isEqualTo(
+ setOf(
+ "emptyFunction" to false,
+ "suspendFunction" to true,
+ "functionWithParams" to false
+ )
+ )
+ }
+ }
+
+ private fun getMetadataElement(invocation: TestInvocation) =
+ invocation.typeElement(TestData::class.java.canonicalName).let {
+ it to KotlinMetadataElement.createFor(Context(invocation.processingEnv), it)!!
+ }
+
+ private class TestData(val constructorParam: String) {
+
+ constructor() : this("anything")
+
+ fun emptyFunction() {}
+
+ suspend fun suspendFunction() {}
+
+ fun functionWithParams(param1: String, yesOrNo: Boolean, number: Int) {}
+ }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
index 51f4184..541834d 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
@@ -25,6 +25,7 @@
import androidx.room.Transaction
import androidx.room.TypeConverters
import androidx.room.Update
+import androidx.room.integration.kotlintestapp.vo.AnswerConverter
import androidx.room.integration.kotlintestapp.vo.Author
import androidx.room.integration.kotlintestapp.vo.Book
import androidx.room.integration.kotlintestapp.vo.BookAuthor
@@ -45,7 +46,7 @@
import java.util.Date
@Dao
-@TypeConverters(DateConverter::class)
+@TypeConverters(DateConverter::class, AnswerConverter::class)
interface BooksDao {
@Insert
@@ -330,4 +331,12 @@
@Query("SELECT dateOfBirth FROM author WHERE authorId = :authorId")
suspend fun getAuthorDateOfBirths(authorId: String): Date
+
+ // see: b/123767877, suspend function with inner class as parameter issues.
+ @Query("SELECT 0 FROM book WHERE bookId = :param")
+ suspend fun getZero(param: AnswerConverter.Answer): Int
+
+ // see: b/123767877, suspend function with inner class as parameter issues.
+ @Query("SELECT 'YES' FROM book")
+ suspend fun getAnswer(): AnswerConverter.Answer
}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AnswerConverter.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AnswerConverter.kt
new file mode 100644
index 0000000..2fd788d
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AnswerConverter.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.room.integration.kotlintestapp.vo
+
+import androidx.room.TypeConverter
+
+class AnswerConverter {
+
+ enum class Answer {
+ YES,
+ NO
+ }
+
+ @TypeConverter
+ fun toAnswer(value: String?) = value?.let { Answer.valueOf(it) }
+
+ @TypeConverter
+ fun fromAnswer(value: Answer?) = value?.name
+}
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageView.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageView.kt
index 0e76607..aaea628 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageView.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageView.kt
@@ -17,14 +17,15 @@
package androidx.viewpager2.widget.swipe
import android.app.Activity
+import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.viewpager2.test.R
-private const val PAGE_COLOR_EVEN = 0xFFFF0000.toInt()
-private const val PAGE_COLOR_ODD = 0xFF0000FF.toInt()
+private val PAGE_COLOR_EVEN = Color.parseColor("#FFAAAA")
+private val PAGE_COLOR_ODD = Color.parseColor("#AAAAFF")
object PageView {
fun inflatePage(parent: ViewGroup): View =
diff --git a/viewpager2/src/androidTest/res/layout/item_test_layout.xml b/viewpager2/src/androidTest/res/layout/item_test_layout.xml
index d5e021b..74a0e03 100644
--- a/viewpager2/src/androidTest/res/layout/item_test_layout.xml
+++ b/viewpager2/src/androidTest/res/layout/item_test_layout.xml
@@ -19,4 +19,5 @@
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:textColor="@color/primary_text_default_material_light"
android:gravity="center"/>