diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..b5f68e08
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+.classpath
+.project
+.settings
+eclipsebin
+
+bin
+gen
+build
+out
+lib
+
+target
+pom.xml.*
+release.properties
+local.properties
+.gradle
+
+.idea
+*.iml
+classes
+
+obj
+
+.DS_Store
+log.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..b0131780
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+v0.1.0 - 11/24/2015
+------------------
+ - Initial version.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..b090ca4f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Uber Technologies, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
index 304360ca..ac5362f9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,120 @@
-Readme
+# Uber Rides Android SDK
+
+Official Android SDK (beta) to support Uber’s deeplinks.
+
+This library allows you to integrate Uber into your Android app.
+
+At a minimum, this SDK is designed to work with Android SDK 16.
+
+## Before you begin
+
+Before using this SDK, register your application on the [Uber Developer Site](https://developer.uber.com/).
+
+## Installation
+
+To use the Uber Rides Android SDK, add the compile dependency with the latest version of the Uber SDK.
+
+### Gradle
+
+Add the Uber Rides Android SDK to your `build.gradle`:
+```gradle
+dependencies {
+ compile 'com.uber.sdk:rides-android:0.1.0'
+}
+```
+
+### Maven
+
+In the `pom.xml` file:
+```xml
+
+ com.uber.sdk
+ rides-android
+ 0.1.0/version>
+
+```
+
+## How to use
+
+You can add a Ride Request Button to your View like you would any other View:
+```java
+RequestButton requestButton = new RequestButton(context);
+requestButton.setClientId("your_client_id");
+layout.addView(requestButton);
+```
+
+This will create a request button with default behavior, with pickup pin set to the user’s current location. The user will need to select a product and input additional information when they are switched over to the Uber application.
+
+You can also add your button through XML:
+```xml
+
+
+
+
+
+```
+
+To use the `uber` custom attribute be sure to add `xmlns:uber="http://schemas.android.com/apk/res-auto"` to your root view element.
+
+### Adding Parameters
+
+We suggest passing additional parameters to make the Uber experience even more seamless for your users. For example, dropoff location parameters can be used to automatically pass the user’s destination information over to the driver:
+```java
+RequestButton requestButton = RequestButton(context);
+requestButton.setClientId(“your_client_id”);
+RideParameters rideParams = new RideParameters.Builder()
+ .setProductID(“abc123-productID”)
+ .setPickupLocation(latitude: “37.770”, longitude: “-122.466”, nickname: “California Academy of Sciences”)
+ .setDropoffLocation(latitude: “37.791”, longitude: “-122.405”, nickname: “Pier 39”)
+ .build();
+requestButton.setRideParameters(rideParams);
+layout.addView(requestButton);
+```
+With all the necessary parameters set, pressing the button will seamlessly prompt a ride request confirmation screen.
+
+### Color Style
+
+The default color has a black background with white text:
+```xml
+
+```
+For a button with a white background and black text:
+```xml
+
+```
+
+## Sample Apps
+
+
+A sample app can be found in the `samples` folder. Alternatively, you can also download a sample from the [releases page](https://github.com/uber/rides-android-sdk/releases/tag/v0.1.0).
+
+Don’t forget to configure the appropriate `res/values/strings.xml` file and add your client ID.
+
+To install the sample app from your IDE, File > New > Import Project and select the extracted folder from the downloaded sample.
+
+## Getting help
+
+Uber developers actively monitor the Uber Tag on StackOverflow. If you need help installing or using the library, you can ask a question there. Make sure to tag your question with `uber-api` and `android`!
+
+For full documentation about our API, visit our Developer Site.
+
+## Contributing
+
+We love contributions. If you’ve found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo. Write a test to show your bug was fixed or the feature works as expected.
+
+## MIT Licensed
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..efaa696c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,175 @@
+apply plugin: 'distribution'
+apply plugin: 'net.researchgate.release'
+apply plugin: 'co.riiid.gradle'
+
+import groovy.text.GStringTemplateEngine
+import org.codehaus.groovy.runtime.DateGroovyMethods
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.1'
+ classpath 'net.researchgate:gradle-release:2.2.2'
+ classpath 'co.riiid:gradle-github-plugin:0.3.1'
+ }
+}
+
+allprojects {
+ apply plugin: 'checkstyle'
+ apply plugin: 'maven'
+
+ ["githubToken", "ossrhUsername", "ossrhPassword",
+ "signing.keyId", "signing.password", "signing.secretKeyRingFile",].each {checkAndDefaultProperty(it)}
+
+ ext.set("unsnapshottedVersion", version.replaceAll("-SNAPSHOT", ""))
+ ext.set("samples", project(":samples").subprojects.collect {it.path})
+ ext.set("isReleaseVersion", !version.endsWith("SNAPSHOT"))
+
+ repositories {
+ jcenter()
+ }
+
+ checkstyle {
+ toolVersion = "6.11.2"
+ }
+
+ task checkstyleMain(type: Checkstyle, overwrite: true) {
+ configFile = new File("{$project.projectDir}/config/checkstyle/checkstyle-main.xml")
+ }
+
+ task checkstyleTest(type: Checkstyle, overwrite: true) {
+ configFile = new File("{$project.projectDir}/config/checkstyle/checkstyle-test.xml")
+ }
+}
+
+def generateReleaseNotes() {
+ def changelogSnippet = generateChangelogSnippet()
+ def model = [title: "Uber Rides Android SDK (Beta) v${unsnapshottedVersion}",
+ date: DateGroovyMethods.format(new Date(), 'MM/dd/yyyy'),
+ snippet: changelogSnippet,
+ assets: project.samples.collect {[
+ title: project(it).name,
+ download: githubDownloadPrefix + "v${unsnapshottedVersion}/"
+ + project(it).name + "-v${unsnapshottedVersion}.zip",
+ description: project(it).description,
+ ]}]
+ def engine = new GStringTemplateEngine()
+ def template = engine.createTemplate(rootProject.file('releasenotes.gtpl')).make(model)
+ return template.toString()
+}
+
+def generateChangelogSnippet() {
+ def changelog = rootProject.file('CHANGELOG.md').text
+ def snippet = ""
+ def stop = false
+ changelog.eachLine {line, count ->
+ if (count >= 2) {
+ stop = stop || line.startsWith("v");
+ if (!stop) {
+ snippet += line + "\n";
+ }
+ }
+ }
+ return " " + snippet.trim();
+}
+
+def checkAndDefaultProperty(prop) {
+ if (!project.hasProperty(prop)) {
+ logger.warn("Add " + prop + " to your ~/.gradle/gradle.properties file.")
+ rootProject.ext.set(prop, prop)
+ }
+}
+
+def checkForChangelogUpdates(task) {
+ def changelogtext = rootProject.file('CHANGELOG.md').text
+ if (!changelogtext.startsWith("v${unsnapshottedVersion} -")) {
+ throw new AssertionError(
+ "Changelog must be updated with v{$unsnapshottedVersion} before release. Please check " +
+ rootProject.file('CHANGELOG.md').absolutePath)
+ }
+}
+
+gradle.taskGraph.afterTask { Task task, TaskState state ->
+ if (task.path.endsWith("release") || task.path.endsWith("githubReleaseZip")
+ || task.path.endsWith("publicrepoDistZip")) {
+ checkForChangelogUpdates(task)
+ }
+}
+
+// Skip signing archives on Jenkins when -SNAPSHOT is being checked in.
+gradle.taskGraph.beforeTask { Task task ->
+ if (task.path.contains("sign") && !ext.isReleaseVersion) {
+ task.enabled = false
+ }
+}
+
+afterReleaseBuild.dependsOn ":sdk:uploadArchives"
+updateVersion.dependsOn ":githubRelease"
+githubRelease.dependsOn project(":samples").subprojects.collect {it.path + ":githubReleaseZip"}
+
+release {
+ failOnCommitNeeded = false
+ failOnPublishNeeded = false
+ failOnSnapshotDependencies = false
+ revertOnFail = true
+ tagTemplate = "v${unsnapshottedVersion}"
+}
+
+github {
+ owner = 'uber'
+ repo = 'rides-android-sdk'
+ token = "${githubToken}"
+ tagName = "v${unsnapshottedVersion}"
+ targetCommitish = 'master'
+ name = "v${unsnapshottedVersion}"
+ body = generateReleaseNotes()
+ assets = project.samples.collect {
+ project(it).buildDir.absolutePath + "/distributions/" + project(it).name +
+ "-v${unsnapshottedVersion}.zip"
+ }
+}
+
+distributions {
+ publicrepo {
+ baseName = 'publicrepo'
+ contents {
+ from(rootDir) {
+ include 'build.gradle'
+ include 'CHANGELOG.md'
+ include 'gradle.properties'
+ include 'gradlew'
+ include 'gradlew.bat'
+ include 'LICENSE'
+ include 'releasenotes.gtpl'
+ include 'settings.gradle'
+ include 'gradle/'
+ }
+
+ from(rootDir) {
+ include 'README.md'
+ filter { String line ->
+ line.replaceAll("_version_", unsnapshottedVersion)
+ }
+ }
+
+ from('sdk') {
+ filesNotMatching("**/*.png") {
+ filter { String line ->
+ line.replaceAll("_version_", unsnapshottedVersion)
+ }
+ }
+ exclude 'build'
+ exclude '*.iml'
+ into 'sdk'
+ }
+
+ from('samples') {
+ exclude '**/build'
+ exclude '**/*.iml'
+ into 'samples'
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..183a8e55
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,5 @@
+group=com.uber.sdk
+groupId=com.uber.sdk
+artifactId=rides-android
+githubDownloadPrefix=https://github.com/uber/rides-android-sdk/releases/download/
+version=0.1.0
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..8c0fb64a
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..123ed8bb
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Oct 12 19:04:32 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..aec99730
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/releasenotes.gtpl b/releasenotes.gtpl
new file mode 100644
index 00000000..3a697ef1
--- /dev/null
+++ b/releasenotes.gtpl
@@ -0,0 +1,6 @@
+${title} - ${date}
+${snippet}
+
+| Download | Description |
+| ------------- |-------------|
+<% assets.each{ asset -> %>| <%= "[" + asset.title + "](" + asset.download + ")" %> | <%= asset.description %> |\n<%}%>
\ No newline at end of file
diff --git a/samples/build.gradle b/samples/build.gradle
new file mode 100644
index 00000000..9f777b0a
--- /dev/null
+++ b/samples/build.gradle
@@ -0,0 +1,36 @@
+subprojects {
+ task githubReleaseZip(type: Zip) {
+ version = "v${unsnapshottedVersion}"
+
+ from('.') {
+ filesNotMatching("**/*.png") {
+ filter { String line ->
+ line.replaceAll("compile project\\(':sdk'\\)",
+ "compile '${groupId}:${artifactId}:${unsnapshottedVersion}'")
+ }
+ }
+ into '.'
+ exclude 'build'
+ exclude '*.iml'
+ }
+
+ from(rootProject.projectDir.absolutePath) {
+ include 'gradle/'
+ include 'gradlew'
+ include 'gradlew.bat'
+ include 'LICENSE'
+ into '.'
+ }
+
+ from('build/poms') {
+ include 'pom-default.xml'
+ rename { String fileName ->
+ fileName.replaceAll('-default', '')
+ }
+ filter { String line ->
+ line.replaceAll('-SNAPSHOT', '')
+ }
+ into '.'
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/request-button-sample/build.gradle b/samples/request-button-sample/build.gradle
new file mode 100644
index 00000000..6de1b9e9
--- /dev/null
+++ b/samples/request-button-sample/build.gradle
@@ -0,0 +1,39 @@
+apply plugin: 'com.android.application'
+
+repositories {
+ jcenter()
+ mavenLocal()
+}
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.1'
+ }
+}
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ defaultConfig {
+ applicationId "com.uber.sdk.android.rides.samples"
+ minSdkVersion 16
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:23.0.1'
+ compile project(':sdk')
+}
diff --git a/samples/request-button-sample/gradle.properties b/samples/request-button-sample/gradle.properties
new file mode 100644
index 00000000..1da43d5b
--- /dev/null
+++ b/samples/request-button-sample/gradle.properties
@@ -0,0 +1 @@
+description=Ride Request Button Sample
\ No newline at end of file
diff --git a/samples/request-button-sample/src/main/AndroidManifest.xml b/samples/request-button-sample/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a28ae321
--- /dev/null
+++ b/samples/request-button-sample/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/request-button-sample/src/main/java/com/uber/sdk/android/rides/samples/SampleActivity.java b/samples/request-button-sample/src/main/java/com/uber/sdk/android/rides/samples/SampleActivity.java
new file mode 100644
index 00000000..ce046c57
--- /dev/null
+++ b/samples/request-button-sample/src/main/java/com/uber/sdk/android/rides/samples/SampleActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides.samples;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+
+import com.uber.sdk.android.rides.RideParameters;
+import com.uber.sdk.android.rides.RequestButton;
+
+/**
+ * Activity that demonstrates how to use a {@link RequestButton}.
+ */
+public class SampleActivity extends AppCompatActivity {
+
+ private static final String DROPOFF_ADDR = "One Embarcadero Center, San Francisco";
+ private static final float DROPOFF_LAT = 37.795079f;
+ private static final float DROPOFF_LONG = -122.397805f;
+ private static final String DROPOFF_NICK = "Embarcadero";
+ private static final String PICKUP_ADDR = "1455 Market Street, San Francisco";
+ private static final float PICKUP_LAT = 37.775304f;
+ private static final float PICKUP_LONG = -122.417522f;
+ private static final String PICKUP_NICK = "Uber HQ";
+ private static final String UBERX_PRODUCT_ID = "a1111c8c-c720-46c3-8534-2fcdd730040d";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_sample);
+
+ String clientId = getString(R.string.client_id);
+ if (clientId.equals("insert_your_client_id_here")) {
+ throw new IllegalArgumentException("Please enter your client ID in client_id in res/values/strings.xml");
+ }
+
+ RequestButton uberButtonBlack = (RequestButton) findViewById(R.id.uber_button_black);
+ RequestButton uberButtonWhite = (RequestButton) findViewById(R.id.uber_button_white);
+
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setProductId(UBERX_PRODUCT_ID)
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR)
+ .build();
+
+ uberButtonBlack.setRideParameters(rideParameters);
+ uberButtonWhite.setRideParameters(rideParameters);
+ }
+}
diff --git a/samples/request-button-sample/src/main/res/drawable-hdpi/uber_sample_ic_launcher.png b/samples/request-button-sample/src/main/res/drawable-hdpi/uber_sample_ic_launcher.png
new file mode 100644
index 00000000..a626ceb4
Binary files /dev/null and b/samples/request-button-sample/src/main/res/drawable-hdpi/uber_sample_ic_launcher.png differ
diff --git a/samples/request-button-sample/src/main/res/drawable-mdpi/uber_sample_ic_launcher.png b/samples/request-button-sample/src/main/res/drawable-mdpi/uber_sample_ic_launcher.png
new file mode 100644
index 00000000..ce495910
Binary files /dev/null and b/samples/request-button-sample/src/main/res/drawable-mdpi/uber_sample_ic_launcher.png differ
diff --git a/samples/request-button-sample/src/main/res/drawable-xhdpi/uber_sample_ic_launcher.png b/samples/request-button-sample/src/main/res/drawable-xhdpi/uber_sample_ic_launcher.png
new file mode 100644
index 00000000..24d5dd00
Binary files /dev/null and b/samples/request-button-sample/src/main/res/drawable-xhdpi/uber_sample_ic_launcher.png differ
diff --git a/samples/request-button-sample/src/main/res/drawable-xxhdpi/uber_sample_ic_launcher.png b/samples/request-button-sample/src/main/res/drawable-xxhdpi/uber_sample_ic_launcher.png
new file mode 100644
index 00000000..138ad4fb
Binary files /dev/null and b/samples/request-button-sample/src/main/res/drawable-xxhdpi/uber_sample_ic_launcher.png differ
diff --git a/samples/request-button-sample/src/main/res/drawable-xxxhdpi/uber_sample_ic_launcher.png b/samples/request-button-sample/src/main/res/drawable-xxxhdpi/uber_sample_ic_launcher.png
new file mode 100644
index 00000000..06aff421
Binary files /dev/null and b/samples/request-button-sample/src/main/res/drawable-xxxhdpi/uber_sample_ic_launcher.png differ
diff --git a/samples/request-button-sample/src/main/res/layout/activity_sample.xml b/samples/request-button-sample/src/main/res/layout/activity_sample.xml
new file mode 100644
index 00000000..b05f44ec
--- /dev/null
+++ b/samples/request-button-sample/src/main/res/layout/activity_sample.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/request-button-sample/src/main/res/values/dimens.xml b/samples/request-button-sample/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..6d8bfe77
--- /dev/null
+++ b/samples/request-button-sample/src/main/res/values/dimens.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ 16dp
+
diff --git a/samples/request-button-sample/src/main/res/values/strings.xml b/samples/request-button-sample/src/main/res/values/strings.xml
new file mode 100644
index 00000000..6505cd59
--- /dev/null
+++ b/samples/request-button-sample/src/main/res/values/strings.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ Uber SDK
+ insert_your_client_id_here
+
diff --git a/sdk/build.gradle b/sdk/build.gradle
new file mode 100644
index 00000000..5df47c0c
--- /dev/null
+++ b/sdk/build.gradle
@@ -0,0 +1,115 @@
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+apply plugin: 'signing'
+apply plugin: 'com.github.dcendents.android-maven'
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+
+ dependencies{
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
+ }
+}
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ useLibrary 'org.apache.http.legacy'
+
+ defaultConfig {
+ minSdkVersion 16
+ versionName version
+ consumerProguardFiles 'proguard.txt'
+ }
+}
+
+task sourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.sourceFiles
+}
+
+task javadoc(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+}
+
+artifacts {
+ archives sourcesJar
+ archives javadocJar
+}
+
+signing {
+ sign configurations.archives
+}
+
+project.archivesBaseName = artifactId
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+ authentication(userName: ossrhUsername, password: ossrhPassword)
+ }
+
+ snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
+ authentication(userName: ossrhUsername, password: ossrhPassword)
+ }
+
+ pom.project {
+ name 'Uber Rides Android SDK (beta)'
+ packaging 'aar'
+ artifactId artifactId
+
+ description 'The official Android SDK (beta) for the Uber Rides API.'
+ url 'https://developer.uber.com'
+
+ scm {
+ connection 'scm:git:git@github.com:uber/rides-android-sdk.git'
+ developerConnection 'scm:git:git@github.com:uber/rides-android-sdk.git'
+ url 'git@github.com:uber/rides-android-sdk.git'
+ }
+
+ licenses {
+ license {
+ name 'MIT License'
+ url 'http://www.opensource.org/licenses/mit-license.php'
+ }
+ }
+
+ developers {
+ developer {
+ id 'arogal'
+ name 'Adam Rogal'
+ email 'arogal@uber.com'
+ }
+
+ developer {
+ id 'itstexter'
+ name 'Alex Texter'
+ email 'texter@uber.com'
+ }
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:23.0.1'
+ compile 'com.google.guava:guava:18.0'
+ compile 'com.google.http-client:google-http-client-jackson2:1.19.0'
+
+ testCompile 'junit:junit:4.12'
+ testCompile "org.mockito:mockito-core:1.9.5"
+ testCompile "org.robolectric:robolectric:3.0"
+}
diff --git a/sdk/proguard.txt b/sdk/proguard.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/sdk/src/main/AndroidManifest.xml b/sdk/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..b6889414
--- /dev/null
+++ b/sdk/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/sdk/src/main/java/com/uber/sdk/android/rides/RequestButton.java b/sdk/src/main/java/com/uber/sdk/android/rides/RequestButton.java
new file mode 100644
index 00000000..7f4e0d06
--- /dev/null
+++ b/sdk/src/main/java/com/uber/sdk/android/rides/RequestButton.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * An Uber styled button to request rides with specific {@link RideParameters}. Default {@link RideParameters} is
+ * set to a pickup of the device's location. Requires a client ID to function.
+ */
+public class RequestButton extends UberButton {
+
+ private static final String USER_AGENT_BUTTON = "rides-button-v0.1.0";
+
+ @NonNull
+ private RideParameters mRideParameters = new RideParameters.Builder().build();
+ @Nullable
+ private String mClientId;
+
+ public RequestButton(Context context) {
+ this(context, null);
+ }
+
+ public RequestButton(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.uberButtonStyle);
+ }
+
+ public RequestButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Sets the {@link RideParameters} that will be used to request a ride when the button is clicked. If null will
+ * use default RideParameters behavior.
+ */
+ public void setRideParameters(@Nullable RideParameters rideParameters) {
+ if (rideParameters == null) {
+ rideParameters = new RideParameters.Builder().build();
+ }
+ mRideParameters = rideParameters;
+ }
+
+ /**
+ * Sets the client ID that is used to power the ride request.
+ */
+ public void setClientId(@NonNull String clientId) {
+ this.mClientId = clientId;
+ }
+
+ @Override
+ protected void init(
+ @NonNull Context context,
+ @Nullable AttributeSet attributeSet,
+ int defStyleAttrs,
+ int defStyleRes) {
+ Style style = Style.DEFAULT;
+ if (attributeSet != null) {
+ TypedArray typedArray = context.getTheme().obtainStyledAttributes(attributeSet,
+ R.styleable.RequestButton, 0, 0);
+ mClientId = typedArray.getString(R.styleable.RequestButton_client_id);
+ style = Style.fromInt(typedArray.getInt(R.styleable.RequestButton_style,
+ Style.DEFAULT.getValue()));
+ typedArray.recycle();
+ }
+ // If no style specified, or just the default UberButton style, use the style attribute
+ defStyleRes = defStyleRes == 0 || defStyleRes == R.style.UberButton ? style.getStyleId() : defStyleRes;
+
+ super.init(context, attributeSet, defStyleAttrs, defStyleRes);
+
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mClientId == null) {
+ throw new IllegalStateException("Client ID required to use RequestButton.");
+ }
+
+ RequestDeeplink requestDeeplink = new RequestDeeplink.Builder()
+ .setClientId(mClientId)
+ .setRideParameters(mRideParameters)
+ .setUserAgent(USER_AGENT_BUTTON)
+ .build();
+
+ requestDeeplink.execute(getContext());
+ }
+ });
+ }
+
+ /**
+ * Encapsulates the valid values for the uber:color_scheme attribute for a {@link RequestButton}
+ */
+ private enum Style {
+ /**
+ * Black background, white text. This is the default.
+ */
+ BLACK(0, R.style.UberButton_RideRequest),
+
+ /**
+ * White background, black text.
+ */
+ WHITE(1, R.style.UberButton_RideRequest_White);
+
+ private static Style DEFAULT = BLACK;
+
+ private int mIntValue;
+ private int mStyleId;
+
+ Style(int value, int styleId) {
+ this.mIntValue = value;
+ this.mStyleId = styleId;
+ }
+
+ /**
+ * If the value is not found returns default Style.
+ */
+ @NonNull
+ static Style fromInt(int enumValue) {
+ for (Style style : values()) {
+ if (style.getValue() == enumValue) {
+ return style;
+ }
+ }
+
+ return DEFAULT;
+ }
+
+ private int getValue() {
+ return mIntValue;
+ }
+
+ private int getStyleId() {
+ return mStyleId;
+ }
+ }
+}
diff --git a/sdk/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java b/sdk/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java
new file mode 100644
index 00000000..b9e6edfd
--- /dev/null
+++ b/sdk/src/main/java/com/uber/sdk/android/rides/RequestDeeplink.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.google.common.base.Preconditions;
+
+
+/**
+ * A deeplink for requesting rides in the Uber application.
+ *
+ * @see Uber deeplink documentation
+ */
+public class RequestDeeplink {
+
+ private static final String UBER_PACKAGE_NAME = "com.ubercab";
+ private static final String UBER_SDK_LOG_TAG = "UberSDK";
+ private static final String USER_AGENT_DEEPLINK = "rides-deeplink-v0.1.0";
+
+ @NonNull private final Uri mUri;
+
+ private RequestDeeplink(@NonNull Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * Executes the deeplink to launch the Uber app. If the app is not installed redirects to the play store.
+ *
+ * @param context The {@link Context} the deeplink will be executed from, used to start a new {@link Activity}.
+ * If not a windowed context will error.
+ */
+ public void execute(@NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ packageManager.getPackageInfo(UBER_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
+ Intent intent = new Intent(Intent.ACTION_VIEW, mUri);
+ context.startActivity(intent);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(UBER_SDK_LOG_TAG, "Uber app not installed, redirecting to mobile sign up.");
+ String redirect = context.getResources().getString(R.string.mobile_redirect);
+ String url = String.format(redirect,
+ mUri.getQueryParameter(Builder.CLIENT_ID), mUri.getQueryParameter(Builder.USER_AGENT));
+ context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ }
+ }
+
+ /**
+ * The {@link Uri} for the deeplink.
+ */
+ @NonNull
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Builder for {@link RequestDeeplink} objects.
+ */
+ public static class Builder {
+
+ public static final String ACTION = "action";
+ public static final String SET_PICKUP = "setPickup";
+ public static final String CLIENT_ID = "client_id";
+ public static final String PRODUCT_ID = "product_id";
+ public static final String MY_LOCATION = "my_location";
+ public static final String LATITUDE = "[latitude]";
+ public static final String LONGITUDE = "[longitude]";
+ public static final String NICKNAME = "[nickname]";
+ public static final String FORMATTED_ADDRESS = "[formatted_address]";
+ public static final String SCHEME = "uber";
+ public static final String USER_AGENT = "user-agent";
+
+ private String mClientId;
+ private String mUserAgent = USER_AGENT_DEEPLINK;
+ private RideParameters mRideParameters;
+
+ /**
+ * Sets the client ID for the app the deeplink is being started from.
+ */
+ public RequestDeeplink.Builder setClientId(@NonNull String cliendId) {
+ mClientId = cliendId;
+ return this;
+ }
+
+ /**
+ * Sets the {@link RideParameters} for the deeplink.
+ */
+ public RequestDeeplink.Builder setRideParameters(@NonNull RideParameters rideParameters) {
+ mRideParameters = rideParameters;
+ return this;
+ }
+
+ /**
+ * Builds an {@link RequestDeeplink} object.
+ */
+ @NonNull
+ public RequestDeeplink build() {
+ validate();
+
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(SCHEME);
+ builder.appendQueryParameter(ACTION, SET_PICKUP);
+ builder.appendQueryParameter(CLIENT_ID, mClientId);
+ if (mRideParameters.getProductId() != null) {
+ builder.appendQueryParameter(PRODUCT_ID, mRideParameters.getProductId());
+ }
+ if (mRideParameters.getPickupLatitude() != null && mRideParameters.getPickupLongitude() != null) {
+ addLocation(LocationType.PICKUP, Float.toString(mRideParameters.getPickupLatitude()),
+ Float.toString(mRideParameters.getPickupLongitude()), mRideParameters.getPickupNickname(),
+ mRideParameters.getPickupAddress(), builder);
+ }
+ if (mRideParameters.isPickupMyLocation()) {
+ builder.appendQueryParameter(LocationType.PICKUP.getUriQueryKey(), MY_LOCATION);
+ }
+ if (mRideParameters.getDropoffLatitude() != null && mRideParameters.getDropoffLongitude() != null) {
+ addLocation(LocationType.DROPOFF, Float.toString(mRideParameters.getDropoffLatitude()),
+ Float.toString(mRideParameters.getDropoffLongitude()), mRideParameters.getDropoffNickname(),
+ mRideParameters.getDropoffAddress(), builder);
+ }
+ if (mUserAgent == null) {
+ mUserAgent = USER_AGENT_DEEPLINK;
+ }
+ builder.appendQueryParameter(USER_AGENT, mUserAgent);
+ return new RequestDeeplink(builder.build());
+ }
+
+ /**
+ * Sets the user agent, describing where this {@link RequestDeeplink} came from for analytics.
+ */
+ RequestDeeplink.Builder setUserAgent(@NonNull String userAgent) {
+ mUserAgent = userAgent;
+ return this;
+ }
+
+ private void addLocation(@NonNull LocationType locationType, @NonNull String latitude,
+ @NonNull String longitude, @Nullable String nickname, @Nullable String address, Uri.Builder builder) {
+ String typeQueryKey = locationType.getUriQueryKey();
+ builder.appendQueryParameter(typeQueryKey + LATITUDE, latitude);
+ builder.appendQueryParameter(typeQueryKey + LONGITUDE, longitude);
+ if (nickname != null) {
+ builder.appendQueryParameter(typeQueryKey + NICKNAME, nickname);
+ }
+ if (address != null) {
+ builder.appendQueryParameter(typeQueryKey + FORMATTED_ADDRESS, address);
+ }
+ }
+
+ private void validate() {
+ Preconditions.checkState(mClientId != null, "Must supply a client ID.");
+ Preconditions.checkState(mRideParameters != null, "Must supply ride parameters.");
+ }
+
+ private enum LocationType {
+ PICKUP,
+ DROPOFF;
+
+ private String getUriQueryKey() {
+ return name().toLowerCase();
+ }
+ }
+ }
+}
diff --git a/sdk/src/main/java/com/uber/sdk/android/rides/RideParameters.java b/sdk/src/main/java/com/uber/sdk/android/rides/RideParameters.java
new file mode 100644
index 00000000..5703f60e
--- /dev/null
+++ b/sdk/src/main/java/com/uber/sdk/android/rides/RideParameters.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.support.annotation.Nullable;
+
+/**
+ * Represents the parameters for an Uber ride.
+ */
+public class RideParameters {
+
+ private final boolean mIsPickupMyLocation;
+ private final String mProductId;
+ private final Float mPickupLatitude;
+ private final Float mPickupLongitude;
+ private final String mPickupNickname;
+ private final String mPickupAddress;
+ private final Float mDropoffLatitude;
+ private final Float mDropoffLongitude;
+ private final String mDropoffNickname;
+ private final String mDropoffAddress;
+
+ private RideParameters(boolean isPickupMyLocation,
+ @Nullable String productId,
+ @Nullable Float pickupLatitude,
+ @Nullable Float pickupLongitude,
+ @Nullable String pickupNickname,
+ @Nullable String pickupAddress,
+ @Nullable Float dropoffLatitude,
+ @Nullable Float dropoffLongitude,
+ @Nullable String dropoffNickname,
+ @Nullable String dropoffAddress) {
+ mIsPickupMyLocation = isPickupMyLocation;
+ mProductId = productId;
+ mPickupLatitude = pickupLatitude;
+ mPickupLongitude = pickupLongitude;
+ mPickupNickname = pickupNickname;
+ mPickupAddress = pickupAddress;
+ mDropoffLatitude = dropoffLatitude;
+ mDropoffLongitude = dropoffLongitude;
+ mDropoffNickname = dropoffNickname;
+ mDropoffAddress = dropoffAddress;
+ }
+
+ /**
+ * @return True if the pickup location of the ride is set to be the device's location, false if a
+ * specific pickup location has been set.
+ */
+ public boolean isPickupMyLocation() {
+ return mIsPickupMyLocation;
+ }
+
+ /**
+ * Gets the product ID for the ride.
+ */
+ @Nullable
+ public String getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * Gets the latitude of the pickup location of the ride. Null if no pickup location specified.
+ */
+ @Nullable
+ public Float getPickupLatitude() {
+ return mPickupLatitude;
+ }
+
+ /**
+ * Gets the longitude of the pickup location of the ride. Null if no pickup location specified.
+ */
+ @Nullable
+ public Float getPickupLongitude() {
+ return mPickupLongitude;
+ }
+
+ /**
+ * Gets the nickname of the pickup location of the ride. Null if no pickup location specified.
+ */
+ @Nullable
+ public String getPickupNickname() {
+ return mPickupNickname;
+ }
+
+ /**
+ * Gets the address of the pickup location of the ride. Null if no pickup location specified.
+ */
+ @Nullable
+ public String getPickupAddress() {
+ return mPickupAddress;
+ }
+
+ /**
+ * Gets the latitude of the dropoff location of the ride. Null if no dropoff location specified.
+ */
+ @Nullable
+ public Float getDropoffLatitude() {
+ return mDropoffLatitude;
+ }
+
+ /**
+ * Gets the longitude of the dropoff location of the ride. Null if no dropoff location specified.
+ */
+ @Nullable
+ public Float getDropoffLongitude() {
+ return mDropoffLongitude;
+ }
+
+ /**
+ * Gets the nickname of the dropoff location of the ride. Null if no dropoff location specified.
+ */
+ @Nullable
+ public String getDropoffNickname() {
+ return mDropoffNickname;
+ }
+
+ /**
+ * Gets the address of the dropoff location of the ride. Null if no dropoff location specified.
+ */
+ @Nullable
+ public String getDropoffAddress() {
+ return mDropoffAddress;
+ }
+
+ /**
+ * Builder for {@link RideParameters} objects.
+ */
+ public static class Builder {
+
+ private boolean mIsPickupMyLocation = true;
+ private String mProductId;
+ private Float mPickupLatitude;
+ private Float mPickupLongitude;
+ private String mPickupNickname;
+ private String mPickupAddress;
+ private Float mDropoffLatitude;
+ private Float mDropoffLongitude;
+ private String mDropoffNickname;
+ private String mDropoffAddress;
+
+ /**
+ * Sets the product ID for the ride.
+ */
+ public RideParameters.Builder setProductId(String productId) {
+ mProductId = productId;
+ return this;
+ }
+
+ /**
+ * Sets the pickup location for the ride. If no pickup is supplied then it defaults to the device's location.
+ *
+ * @param latitude The latitude of the pickup.
+ * @param longitude The longitude of the pickup.
+ * @param nickname This will show up as the text name at the request a ride screen in the Uber app. If not
+ * supplied will just show address.
+ * @param address The address of the pickup location. If not supplied the bar will read 'Go to pin'.
+ */
+ public RideParameters.Builder setPickupLocation(float latitude, float longitude, @Nullable String nickname,
+ @Nullable String address) {
+ mPickupLatitude = latitude;
+ mPickupLongitude = longitude;
+ mPickupNickname = nickname;
+ mPickupAddress = address;
+ mIsPickupMyLocation = false;
+ return this;
+ }
+
+ /**
+ * Sets the dropoff location for the ride.
+ *
+ * @param latitude The latitude of the dropoff.
+ * @param longitude The longitude of the dropoff.
+ * @param nickname This will show up as the text name at the request a ride screen in the Uber app. If not
+ * supplied will just show address.
+ * @param address The address of the dropoff location. If not supplied will read 'Destination'.
+ */
+ public RideParameters.Builder setDropoffLocation(float latitude, float longitude, @Nullable String nickname,
+ @Nullable String address) {
+ mDropoffLatitude = latitude;
+ mDropoffLongitude = longitude;
+ mDropoffNickname = nickname;
+ mDropoffAddress = address;
+ return this;
+ }
+
+ /**
+ * Sets the pickup location for the ride to be the device's current location.
+ */
+ public RideParameters.Builder setPickupToMyLocation() {
+ mIsPickupMyLocation = true;
+ mPickupLatitude = null;
+ mPickupLongitude = null;
+ mPickupNickname = null;
+ mPickupAddress = null;
+ return this;
+ }
+
+ /**
+ * Builds an {@link RideParameters} object.
+ */
+ public RideParameters build() {
+ return new RideParameters(mIsPickupMyLocation, mProductId, mPickupLatitude, mPickupLongitude,
+ mPickupNickname, mPickupAddress, mDropoffLatitude, mDropoffLongitude, mDropoffNickname,
+ mDropoffAddress);
+ }
+ }
+}
diff --git a/sdk/src/main/java/com/uber/sdk/android/rides/UberButton.java b/sdk/src/main/java/com/uber/sdk/android/rides/UberButton.java
new file mode 100644
index 00000000..73a8e233
--- /dev/null
+++ b/sdk/src/main/java/com/uber/sdk/android/rides/UberButton.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.widget.Button;
+
+/**
+ * {@link android.widget.Button} that can be used as a button and provides default Uber styling.
+ */
+public class UberButton extends Button {
+
+ private static final int DEFAULT_TEXT_SIZE = 18;
+
+ /**
+ * Constructor.
+ *
+ * @param context the context creating the view.
+ */
+ public UberButton(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context the context creating the view.
+ * @param attrs attributes for the view.
+ */
+ public UberButton(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.uberButtonStyle);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context the context creating the view.
+ * @param attrs attributes for the view.
+ * @param defStyleAttr the default attribute to use for a style if none is specified.
+ */
+ public UberButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context the context creating the view.
+ * @param attrs attributes for the view.
+ * @param defStyleAttr the default attribute to use for a style if none is specified.
+ * @param defStyleRes the default style, used only if defStyleAttr is 0 or can not be found in the theme.
+ */
+ public UberButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr);
+ defStyleRes = defStyleRes == 0 ? R.style.UberButton : defStyleRes;
+ init(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ protected void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ setBackgroundAttributes(context, attrs, defStyleAttr, defStyleRes);
+ setDrawableAttributes(context, attrs, defStyleAttr, defStyleRes);
+ setPaddingAttributes(context, attrs, defStyleAttr, defStyleRes);
+ setTextAttributes(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void setBackgroundAttributes(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ int attrsResources[] = {
+ android.R.attr.background,
+ };
+ TypedArray backgroundAttributes = context.getTheme().obtainStyledAttributes(
+ attrs,
+ attrsResources,
+ defStyleAttr,
+ defStyleRes);
+ try {
+ if (backgroundAttributes.hasValue(0)) {
+ int backgroundResource = backgroundAttributes.getResourceId(0, 0);
+ if (backgroundResource != 0) {
+ setBackgroundResource(backgroundResource);
+ } else {
+ setBackgroundColor(backgroundAttributes.getColor(0, Color.BLACK));
+ }
+ } else {
+ setBackgroundColor(backgroundAttributes.getColor(0, Color.BLACK));
+ }
+ } finally {
+ backgroundAttributes.recycle();
+ }
+ }
+
+ private void setDrawableAttributes(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ int attrsResources[] = {
+ android.R.attr.drawableLeft,
+ android.R.attr.drawableTop,
+ android.R.attr.drawableRight,
+ android.R.attr.drawableBottom,
+ android.R.attr.drawablePadding,
+ };
+ TypedArray drawableAttributes = context.getTheme().obtainStyledAttributes(
+ attrs,
+ attrsResources,
+ defStyleAttr,
+ defStyleRes);
+ try {
+ setCompoundDrawablesWithIntrinsicBounds(
+ drawableAttributes.getResourceId(0, 0),
+ drawableAttributes.getResourceId(1, 0),
+ drawableAttributes.getResourceId(2, 0),
+ drawableAttributes.getResourceId(3, 0));
+ setCompoundDrawablePadding(drawableAttributes.getDimensionPixelSize(4, 0));
+ } finally {
+ drawableAttributes.recycle();
+ }
+ }
+
+ private void setPaddingAttributes(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ int attrsResources[] = {
+ android.R.attr.padding,
+ android.R.attr.paddingLeft,
+ android.R.attr.paddingTop,
+ android.R.attr.paddingRight,
+ android.R.attr.paddingBottom,
+ };
+ TypedArray paddingAttributes = context.getTheme().obtainStyledAttributes(
+ attrs,
+ attrsResources,
+ defStyleAttr,
+ defStyleRes);
+ try {
+ int padding = paddingAttributes.getDimensionPixelOffset(0, 0);
+ int paddingLeft = paddingAttributes.getDimensionPixelSize(1, 0);
+ paddingLeft = paddingLeft == 0 ? padding : paddingLeft;
+ int paddingTop = paddingAttributes.getDimensionPixelSize(2, 0);
+ paddingTop = paddingTop == 0 ? padding : paddingTop;
+ int paddingRight = paddingAttributes.getDimensionPixelSize(3, 0);
+ paddingRight = paddingRight == 0 ? padding : paddingRight;
+ int paddingBottom = paddingAttributes.getDimensionPixelSize(4, 0);
+ paddingBottom = paddingBottom == 0 ? padding : paddingBottom;
+ setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ } finally {
+ paddingAttributes.recycle();
+ }
+ }
+
+ private void setTextAttributes(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ int attrsResources[] = {
+ android.R.attr.textColor,
+ android.R.attr.gravity,
+ android.R.attr.textSize,
+ android.R.attr.textStyle,
+ android.R.attr.text,
+ };
+ TypedArray textAttributes = context.getTheme().obtainStyledAttributes(
+ attrs,
+ attrsResources,
+ defStyleAttr,
+ defStyleRes);
+ try {
+ setTextColor(textAttributes.getColor(0, Color.WHITE));
+ setGravity(textAttributes.getInt(1, Gravity.CENTER));
+ setTextSize(TypedValue.COMPLEX_UNIT_DIP, textAttributes.getDimensionPixelSize(2, DEFAULT_TEXT_SIZE));
+ setTypeface(Typeface.defaultFromStyle(textAttributes.getInt(3, Typeface.NORMAL)));
+ String text = textAttributes.getString(4);
+ if (text != null) {
+ setText(textAttributes.getString(4));
+ }
+ } finally {
+ textAttributes.recycle();
+ }
+ }
+}
diff --git a/sdk/src/main/res/drawable-hdpi/uber_badge.png b/sdk/src/main/res/drawable-hdpi/uber_badge.png
new file mode 100644
index 00000000..fd00ad07
Binary files /dev/null and b/sdk/src/main/res/drawable-hdpi/uber_badge.png differ
diff --git a/sdk/src/main/res/drawable-mdpi/uber_badge.png b/sdk/src/main/res/drawable-mdpi/uber_badge.png
new file mode 100644
index 00000000..a2c72fca
Binary files /dev/null and b/sdk/src/main/res/drawable-mdpi/uber_badge.png differ
diff --git a/sdk/src/main/res/drawable-xhdpi/uber_badge.png b/sdk/src/main/res/drawable-xhdpi/uber_badge.png
new file mode 100644
index 00000000..b1abcf8c
Binary files /dev/null and b/sdk/src/main/res/drawable-xhdpi/uber_badge.png differ
diff --git a/sdk/src/main/res/drawable-xxhdpi/uber_badge.png b/sdk/src/main/res/drawable-xxhdpi/uber_badge.png
new file mode 100644
index 00000000..f11410e1
Binary files /dev/null and b/sdk/src/main/res/drawable-xxhdpi/uber_badge.png differ
diff --git a/sdk/src/main/res/drawable-xxxhdpi/uber_badge.png b/sdk/src/main/res/drawable-xxxhdpi/uber_badge.png
new file mode 100644
index 00000000..6b297882
Binary files /dev/null and b/sdk/src/main/res/drawable-xxxhdpi/uber_badge.png differ
diff --git a/sdk/src/main/res/drawable/uber_button_background_black_100.xml b/sdk/src/main/res/drawable/uber_button_background_black_100.xml
new file mode 100644
index 00000000..7483056c
--- /dev/null
+++ b/sdk/src/main/res/drawable/uber_button_background_black_100.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/drawable/uber_button_background_black_90.xml b/sdk/src/main/res/drawable/uber_button_background_black_90.xml
new file mode 100644
index 00000000..7ec3af6a
--- /dev/null
+++ b/sdk/src/main/res/drawable/uber_button_background_black_90.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/drawable/uber_button_background_selector_black.xml b/sdk/src/main/res/drawable/uber_button_background_selector_black.xml
new file mode 100644
index 00000000..e6e827e9
--- /dev/null
+++ b/sdk/src/main/res/drawable/uber_button_background_selector_black.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/drawable/uber_button_background_selector_white.xml b/sdk/src/main/res/drawable/uber_button_background_selector_white.xml
new file mode 100644
index 00000000..9d8eb7bb
--- /dev/null
+++ b/sdk/src/main/res/drawable/uber_button_background_selector_white.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/drawable/uber_button_background_white_100.xml b/sdk/src/main/res/drawable/uber_button_background_white_100.xml
new file mode 100644
index 00000000..7a6d2a28
--- /dev/null
+++ b/sdk/src/main/res/drawable/uber_button_background_white_100.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/drawable/uber_button_background_white_80.xml b/sdk/src/main/res/drawable/uber_button_background_white_80.xml
new file mode 100644
index 00000000..4734b752
--- /dev/null
+++ b/sdk/src/main/res/drawable/uber_button_background_white_80.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/values/attrs.xml b/sdk/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..dd0aa31b
--- /dev/null
+++ b/sdk/src/main/res/values/attrs.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/values/colors.xml b/sdk/src/main/res/values/colors.xml
new file mode 100644
index 00000000..26c5cb64
--- /dev/null
+++ b/sdk/src/main/res/values/colors.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ #09091A
+ #222231
+
+ #C0C0C8
+ #CDCDD3
+
diff --git a/sdk/src/main/res/values/dimens.xml b/sdk/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..2a0a9785
--- /dev/null
+++ b/sdk/src/main/res/values/dimens.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ 20sp
+ 8dp
+ 2dp
+
diff --git a/sdk/src/main/res/values/strings.xml b/sdk/src/main/res/values/strings.xml
new file mode 100644
index 00000000..c543de48
--- /dev/null
+++ b/sdk/src/main/res/values/strings.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ Ride there with Uber
+ https://m.uber.com/sign-up?client_id=%1$s&user-agent=%2$s
+
diff --git a/sdk/src/main/res/values/styles.xml b/sdk/src/main/res/values/styles.xml
new file mode 100644
index 00000000..bda64f49
--- /dev/null
+++ b/sdk/src/main/res/values/styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
diff --git a/sdk/src/main/res/values/themes.xml b/sdk/src/main/res/values/themes.xml
new file mode 100644
index 00000000..3970822d
--- /dev/null
+++ b/sdk/src/main/res/values/themes.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/sdk/src/test/java/com/uber/sdk/android/rides/RequestButtonTest.java b/sdk/src/test/java/com/uber/sdk/android/rides/RequestButtonTest.java
new file mode 100644
index 00000000..cbe32401
--- /dev/null
+++ b/sdk/src/test/java/com/uber/sdk/android/rides/RequestButtonTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.support.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.builder.RobolectricPackageManager;
+import org.robolectric.shadows.ShadowActivity;
+
+import java.io.IOException;
+
+import static com.uber.sdk.android.rides.TestUtils.readUriResourceWithUserAgentParam;
+import static org.junit.Assert.assertEquals;
+import static org.robolectric.Shadows.shadowOf;
+
+/**
+ * Tests {@link RequestButton}
+ */
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 21)
+public class RequestButtonTest {
+
+ private static final String CLIENT_ID = "clientId";
+ private static final float PICKUP_LAT = 32.1234f;
+ private static final float PICKUP_LONG = -122.3456f;
+ private static final String PICKUP_NICK = "pickupNick";
+ private static final String PICKUP_ADDR = "Pickup Address";
+ private static final String UBER_PACKAGE_NAME = "com.ubercab";
+ private static final String USER_AGENT_BUTTON = "rides-button-v0.1.0";
+
+ @Rule public ExpectedException exception = ExpectedException.none();
+
+ private Activity mActivity;
+ private RequestButton mRequestButton;
+
+ @Before
+ public void setup() {
+ mActivity = Robolectric.setupActivity(Activity.class);
+ mRequestButton = new RequestButton(mActivity);
+ }
+
+ @Test
+ public void onClick_whenNullClientId_shouldThrowException() {
+ exception.expect(RuntimeException.class);
+ exception.expectMessage("Client ID required to use RequestButton.");
+
+ mRequestButton.performClick();
+ }
+
+ @Test
+ public void onClick_whenClientIdProvidedAndNoUberApp_shouldStartMobileSite() throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/no_app_installed",
+ USER_AGENT_BUTTON);
+
+ ShadowActivity shadowActivity = setupShadowActivityWithUber(false);
+
+ mRequestButton.setClientId(CLIENT_ID);
+ mRequestButton.performClick();
+
+ Intent shadowedIntent = shadowActivity.getNextStartedActivity();
+ assertEquals(expectedUri, shadowedIntent.getData().toString());
+ }
+
+ @Test
+ public void onClick_whenClientIdProvidedAndUberAppInstalled_shouldStartUberApp() throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/just_client_provided",
+ USER_AGENT_BUTTON);
+
+ ShadowActivity shadowActivity = setupShadowActivityWithUber(true);
+
+ mRequestButton.setClientId(CLIENT_ID);
+ mRequestButton.performClick();
+
+ Intent shadowedIntent = shadowActivity.getNextStartedActivity();
+ assertEquals(expectedUri, shadowedIntent.getDataString());
+ }
+
+ @Test
+ public void onClick_whenClientIdAndPickupProvidedAndUberAppInstalled_shouldStartUberAppWithParams()
+ throws IOException {
+ String path = "src/test/resources/deeplinkuris/pickup_and_client_provided";
+ String expectedUri = readUriResourceWithUserAgentParam(path, USER_AGENT_BUTTON);
+
+ ShadowActivity shadowActivity = setupShadowActivityWithUber(true);
+
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .build();
+ mRequestButton.setClientId(CLIENT_ID);
+ mRequestButton.setRideParameters(rideParameters);
+ mRequestButton.performClick();
+
+ Intent shadowedIntent = shadowActivity.getNextStartedActivity();
+ assertEquals(expectedUri, shadowedIntent.getData().toString());
+ }
+
+ @NonNull
+ private ShadowActivity setupShadowActivityWithUber(boolean isUberInstalled) {
+ ShadowActivity shadowActivity = shadowOf(mActivity);
+ if (isUberInstalled) {
+ RobolectricPackageManager packageManager = (RobolectricPackageManager) shadowActivity.getPackageManager();
+
+ PackageInfo uberPackage = new PackageInfo();
+ uberPackage.packageName = UBER_PACKAGE_NAME;
+ packageManager.addPackage(uberPackage);
+ }
+ return shadowActivity;
+ }
+}
diff --git a/sdk/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java b/sdk/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java
new file mode 100644
index 00000000..42215c95
--- /dev/null
+++ b/sdk/src/test/java/com/uber/sdk/android/rides/RequestDeeplinkTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.builder.RobolectricPackageManager;
+import org.robolectric.shadows.ShadowActivity;
+
+import java.io.IOException;
+
+import static com.uber.sdk.android.rides.TestUtils.readUriResourceWithUserAgentParam;
+import static org.junit.Assert.assertEquals;
+import static org.robolectric.Shadows.shadowOf;
+
+/**
+ * Tests {@link RequestDeeplink}
+ */
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 21)
+public class RequestDeeplinkTest {
+
+ private static final String UBER_PACKAGE_NAME = "com.ubercab";
+ private static final String CLIENT_ID = "clientId";
+ private static final String PRODUCT_ID = "productId";
+ private static final float PICKUP_LAT = 32.1234f;
+ private static final float PICKUP_LONG = -122.3456f;
+ private static final String PICKUP_NICK = "pickupNick";
+ private static final String PICKUP_ADDR = "Pickup Address";
+ private static final float DROPOFF_LAT = 32.5678f;
+ private static final float DROPOFF_LONG = -122.6789f;
+ private static final String DROPOFF_NICK = "pickupNick";
+ private static final String DROPOFF_ADDR = "Dropoff Address";
+ private static final String USER_AGENT_DEEPLINK = "rides-deeplink-v0.1.0";
+
+ @Rule public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void onBuildDeeplink_whenClientIdAndDefaultRideParamsProvided_shouldHaveDefaults() throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/just_client_provided",
+ USER_AGENT_DEEPLINK);
+
+ RideParameters rideParameters = new RideParameters.Builder().build();
+ RequestDeeplink deeplink = new RequestDeeplink.Builder()
+ .setRideParameters(rideParameters)
+ .setClientId(CLIENT_ID)
+ .build();
+
+ assertEquals("URI does not match.", expectedUri, deeplink.getUri().toString());
+ }
+
+ @Test
+ public void onBuildDeeplink_whenFullRideParamsProvided_shouldCompleteUri() throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/full_details_uri",
+ USER_AGENT_DEEPLINK);
+
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR)
+ .setProductId(PRODUCT_ID)
+ .build();
+ RequestDeeplink deeplink = new RequestDeeplink.Builder()
+ .setRideParameters(rideParameters)
+ .setClientId(CLIENT_ID)
+ .build();
+
+ assertEquals("URI does not match.", expectedUri, deeplink.getUri().toString());
+ }
+
+ @Test
+ public void onBuildDeeplink_whenPickupAndClientIdProvided_shouldNotHaveDropoffOrProduct()
+ throws IOException {
+ String path = "src/test/resources/deeplinkuris/pickup_and_client_provided";
+ String expectedUri = readUriResourceWithUserAgentParam(path, USER_AGENT_DEEPLINK);
+
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .build();
+ RequestDeeplink deeplink = new RequestDeeplink.Builder()
+ .setRideParameters(rideParameters)
+ .setClientId(CLIENT_ID)
+ .build();
+
+ assertEquals("URI does not match.", expectedUri, deeplink.getUri().toString());
+ }
+
+ @Test
+ public void onBuildDeeplink_whenDropoffClientIdAndProductIdProvided_shouldHaveDefaultPickupAndFullDropoff()
+ throws IOException {
+ String path = "src/test/resources/deeplinkuris/dropoff_client_and_product_provided";
+ String expectedUri = readUriResourceWithUserAgentParam(path, USER_AGENT_DEEPLINK);
+
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setProductId(PRODUCT_ID)
+ .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR)
+ .build();
+ RequestDeeplink deeplink = new RequestDeeplink.Builder()
+ .setRideParameters(rideParameters)
+ .setClientId(CLIENT_ID)
+ .build();
+
+ assertEquals("URI does not match.", expectedUri, deeplink.getUri().toString());
+ }
+
+ @Test
+ public void onBuildDeeplink_whenNoNicknameOrAddressProvided_shouldNotHaveNicknameAndAddress()
+ throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/no_nickname_or_address",
+ USER_AGENT_DEEPLINK);
+
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setProductId(PRODUCT_ID)
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, null, null)
+ .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, null, null)
+ .build();
+ RequestDeeplink deeplink = new RequestDeeplink.Builder()
+ .setRideParameters(rideParameters)
+ .setClientId(CLIENT_ID)
+ .build();
+
+ assertEquals("URI does not match.", expectedUri, deeplink.getUri().toString());
+ }
+
+ @Test
+ public void onBuildDeeplink_whenNoClientId_shouldNotBuild() {
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Must supply a client ID.");
+
+ RideParameters rideParameters = new RideParameters.Builder().build();
+ new RequestDeeplink.Builder().setRideParameters(rideParameters).build();
+ }
+
+ @Test
+ public void onBuildDeeplink_whenNoRideParams_shouldNotBuild() {
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Must supply ride parameters.");
+
+ new RequestDeeplink.Builder().setClientId(CLIENT_ID).build();
+ }
+
+ @Test
+ public void execute_whenNoUberApp_shouldPointToMobileSite() throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/no_app_installed",
+ USER_AGENT_DEEPLINK);
+
+ Activity activity = Robolectric.setupActivity(Activity.class);
+ ShadowActivity shadowActivity = shadowOf(activity);
+
+ RideParameters rideParameters = new RideParameters.Builder().build();
+
+ RequestDeeplink requestDeeplink = new RequestDeeplink.Builder()
+ .setClientId(CLIENT_ID)
+ .setRideParameters(rideParameters)
+ .build();
+ requestDeeplink.execute(activity);
+
+ Intent startedIntent = shadowActivity.getNextStartedActivity();
+ assertEquals(expectedUri, startedIntent.getData().toString());
+ }
+
+ @Test
+ public void execute_whenUberAppInsalled_shouldPointToUberApp() throws IOException {
+ String expectedUri = readUriResourceWithUserAgentParam("src/test/resources/deeplinkuris/just_client_provided",
+ USER_AGENT_DEEPLINK);
+
+ Activity activity = Robolectric.setupActivity(Activity.class);
+ ShadowActivity shadowActivity = shadowOf(activity);
+
+ RobolectricPackageManager packageManager = (RobolectricPackageManager) shadowActivity.getPackageManager();
+
+ PackageInfo uberPackage = new PackageInfo();
+ uberPackage.packageName = UBER_PACKAGE_NAME;
+ packageManager.addPackage(uberPackage);
+
+ RideParameters rideParameters = new RideParameters.Builder().build();
+
+ RequestDeeplink requestDeeplink = new RequestDeeplink.Builder()
+ .setClientId(CLIENT_ID)
+ .setRideParameters(rideParameters)
+ .build();
+
+ requestDeeplink.execute(activity);
+
+ Intent startedIntent = shadowActivity.getNextStartedActivity();
+ assertEquals(expectedUri, startedIntent.getData().toString());
+ }
+}
diff --git a/sdk/src/test/java/com/uber/sdk/android/rides/RideParametersTest.java b/sdk/src/test/java/com/uber/sdk/android/rides/RideParametersTest.java
new file mode 100644
index 00000000..9c98ed20
--- /dev/null
+++ b/sdk/src/test/java/com/uber/sdk/android/rides/RideParametersTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link RideParameters}
+ */
+public class RideParametersTest {
+
+ public static final String PRODUCT_ID = "productId";
+ private static final float PICKUP_LAT = 32.1234f;
+ private static final float PICKUP_LONG = -122.3456f;
+ private static final String PICKUP_NICK = "pickupNick";
+ private static final String PICKUP_ADDR = "Pickup Address";
+ private static final float DROPOFF_LAT = 32.5678f;
+ private static final float DROPOFF_LONG = -122.6789f;
+ private static final String DROPOFF_NICK = "pickupNick";
+ private static final String DROPOFF_ADDR = "Dropoff Address";
+
+ @Test
+ public void onBuildRideParams_whenNothingSet_shouldHaveDefaults() {
+ RideParameters rideParameters = new RideParameters.Builder().build();
+
+ assertDefaults(rideParameters);
+ }
+
+ @Test
+ public void onBuildRideParams_whenAllSet_shouldBeFilled() {
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setProductId(PRODUCT_ID)
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .setDropoffLocation(DROPOFF_LAT, DROPOFF_LONG, DROPOFF_NICK, DROPOFF_ADDR)
+ .build();
+
+ assertFalse(rideParameters.isPickupMyLocation());
+ assertEquals("Product ID does not match.", PRODUCT_ID, rideParameters.getProductId());
+
+ assertEquals("Pickup latitude does not match.", Float.valueOf(PICKUP_LAT), rideParameters.getPickupLatitude());
+ assertEquals("Pickup longitude does not match.", Float.valueOf(PICKUP_LONG),
+ rideParameters.getPickupLongitude());
+ assertEquals("Pickup nickname does not match.", PICKUP_NICK, rideParameters.getPickupNickname());
+ assertEquals("Pickup address does not match.", PICKUP_ADDR, rideParameters.getPickupAddress());
+
+ assertEquals("Dropoff latitude does not match.", Float.valueOf(DROPOFF_LAT),
+ rideParameters.getDropoffLatitude());
+ assertEquals("Dropoff longitude does not match.", Float.valueOf(DROPOFF_LONG),
+ rideParameters.getDropoffLongitude());
+ assertEquals("Dropoff nickname does not match.", DROPOFF_NICK, rideParameters.getDropoffNickname());
+ assertEquals("Dropoff address does not match.", DROPOFF_ADDR, rideParameters.getDropoffAddress());
+ }
+
+ @Test
+ public void onBuildRideParams_whenJustSetToMyLocation_shouldEqualDefaults() {
+ RideParameters rideParameters = new RideParameters.Builder().setPickupToMyLocation().build();
+
+ assertDefaults(rideParameters);
+ }
+
+ @Test
+ public void onBuildRideParams_whenSetPickupLocationAndThenPickupToMyLocation_shouldHavePickupAsMyLocation() {
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .setPickupToMyLocation()
+ .build();
+
+ assertDefaults(rideParameters);
+ }
+
+ @Test
+ public void onBuildRideParams_whenSetPickupToMyLocationAndThenAPickupLocation_shouldHavePickupAsMyLocation() {
+ RideParameters rideParameters = new RideParameters.Builder()
+ .setPickupToMyLocation()
+ .setPickupLocation(PICKUP_LAT, PICKUP_LONG, PICKUP_NICK, PICKUP_ADDR)
+ .build();
+
+ assertFalse(rideParameters.isPickupMyLocation());
+ assertNull(rideParameters.getProductId());
+
+ assertEquals("Pickup latitude does not match.", Float.valueOf(PICKUP_LAT), rideParameters.getPickupLatitude());
+ assertEquals("Pickup longitude does not match.", Float.valueOf(PICKUP_LONG),
+ rideParameters.getPickupLongitude());
+ assertEquals("Pickup nickname does not match.", PICKUP_NICK, rideParameters.getPickupNickname());
+ assertEquals("Pickup address does not match.", PICKUP_ADDR, rideParameters.getPickupAddress());
+
+ assertNull(rideParameters.getDropoffLatitude());
+ assertNull(rideParameters.getDropoffLongitude());
+ assertNull(rideParameters.getDropoffNickname());
+ assertNull(rideParameters.getDropoffAddress());
+ }
+
+ private void assertDefaults(RideParameters rideParameters) {
+ assertTrue(rideParameters.isPickupMyLocation());
+ assertNull(rideParameters.getProductId());
+
+ assertNull(rideParameters.getPickupLatitude());
+ assertNull(rideParameters.getPickupLongitude());
+ assertNull(rideParameters.getPickupNickname());
+ assertNull(rideParameters.getPickupAddress());
+
+ assertNull(rideParameters.getDropoffLatitude());
+ assertNull(rideParameters.getDropoffLongitude());
+ assertNull(rideParameters.getDropoffNickname());
+ assertNull(rideParameters.getDropoffAddress());
+ }
+}
diff --git a/sdk/src/test/java/com/uber/sdk/android/rides/TestUtils.java b/sdk/src/test/java/com/uber/sdk/android/rides/TestUtils.java
new file mode 100644
index 00000000..11581375
--- /dev/null
+++ b/sdk/src/test/java/com/uber/sdk/android/rides/TestUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.support.annotation.NonNull;
+
+import com.google.api.client.repackaged.com.google.common.base.Joiner;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Adds utility methods common amongst multiple test classes.
+ */
+public final class TestUtils {
+
+ /**
+ * Reads the file out as a string, trimming the result.
+ *
+ * @param path The root path of the File. If unable to access will dig down to module level.
+ */
+ public static String readProjectResource(String path) throws IOException {
+ File file = new File(path);
+ if (!file.exists()) {
+ // If not at full path, check module dir
+ List list = Arrays.asList(path.split(File.pathSeparator));
+ file = new File(Joiner.on(File.pathSeparatorChar).join(list.subList(1, list.size())));
+ }
+ return Files.toString(file, StandardCharsets.UTF_8).trim();
+ }
+
+ /**
+ * Reads a URI stored as a text resource file and then adds the user agent.
+ *
+ * @param path The root path of the File. If unable to access will dig down to module level.
+ * @param userAgent The user-agent to be added as a query parameter to the resulting URI.
+ */
+ @NonNull
+ public static String readUriResourceWithUserAgentParam(@NonNull String path, @NonNull String userAgent)
+ throws IOException {
+ String uri = readProjectResource(path);
+ return uri.concat(String.format("&user-agent=%s", userAgent));
+ }
+}
diff --git a/sdk/src/test/java/com/uber/sdk/android/rides/UberButtonTest.java b/sdk/src/test/java/com/uber/sdk/android/rides/UberButtonTest.java
new file mode 100644
index 00000000..9493b9be
--- /dev/null
+++ b/sdk/src/test/java/com/uber/sdk/android/rides/UberButtonTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2015 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.sdk.android.rides;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+
+import org.apache.maven.artifact.ant.shaded.StringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.Attribute;
+import org.robolectric.shadows.CoreShadowsAdapter;
+import org.robolectric.shadows.RoboAttributeSet;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link UberButton}
+ */
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = BuildConfig.class, sdk = 21)
+public class UberButtonTest {
+
+ private static final String ANDROID_ATTR_BACKGROUND = "android:attr/background";
+ private static final String ANDROID_ATTR_DRAWABLE_LEFT = "android:attr/drawableLeft";
+ private static final String ANDROID_ATTR_DRAWABLE_TOP = "android:attr/drawableTop";
+ private static final String ANDROID_ATTR_DRAWABLE_RIGHT = "android:attr/drawableRight";
+ private static final String ANDROID_ATTR_DRAWABLE_BOTTOM = "android:attr/drawableBottom";
+ private static final String ANDROID_ATTR_DRAWABLE_PADDING = "android:attr/drawablePadding";
+ private static final String ANDROID_ATTR_GRAVITY = "android:attr/gravity";
+ private static final String ANDROID_ATTR_PADDING = "android:attr/padding";
+ private static final String ANDROID_ATTR_PADDING_LEFT = "android:attr/paddingLeft";
+ private static final String ANDROID_ATTR_PADDING_TOP = "android:attr/paddingTop";
+ private static final String ANDROID_ATTR_PADDING_RIGHT = "android:attr/paddingRight";
+ private static final String ANDROID_ATTR_PADDING_BOTTOM = "android:attr/paddingBottom";
+ private static final String ANDROID_ATTR_TEXT_COLOR = "android:attr/textColor";
+ private static final String ANDROID_ATTR_TEXT_SIZE = "android:attr/textSize";
+ private static final String ANDROID_ATTR_TEXT_STYLE = "android:attr/textStyle";
+ private static final String ANDROID_ATTR_TEXT = "android:attr/text";
+
+ private static final String ANDROID_COLOR_BLACK = "@android:color/black";
+ private static final String ANDROID_COLOR_WHITE = "@android:color/white";
+ private static final String DRAWABLE_UBER_BADGE = "@drawable/uber_badge";
+ private static final String GRAVITY_END = "end";
+ private static final String STYLE_ITALIC = "italic";
+ private static final String ONE_SP = "1sp";
+ private static final String TWO_SP = "2sp";
+ private static final String THREE_SP = "3sp";
+ private static final String FOUR_SP = "4sp";
+ private static final String TEXT = "test";
+
+ private static final String UBER_PACKAGE_NAME = "com.uber.sdk.android.rides";
+
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ mContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void onCreate_whenBackgroundAttributeSet_shouldSetBackground() {
+ AttributeSet attributeSet = makeAttributeSet(
+ makeAttribute(ANDROID_ATTR_BACKGROUND, ANDROID_COLOR_WHITE)
+ );
+ UberButton uberButton = new UberButton(mContext, attributeSet, 0, 0);
+ assertEquals(Color.WHITE, ((ColorDrawable) uberButton.getBackground()).getColor());
+ }
+
+ @Test
+ public void onCreate_whenCompoundDrawablesAndPaddingSet_shouldSetCompoundDrawableAttributes() {
+ AttributeSet attributeSet = makeAttributeSet(
+ makeAttribute(ANDROID_ATTR_DRAWABLE_LEFT, DRAWABLE_UBER_BADGE),
+ makeAttribute(ANDROID_ATTR_DRAWABLE_TOP, DRAWABLE_UBER_BADGE),
+ makeAttribute(ANDROID_ATTR_DRAWABLE_RIGHT, DRAWABLE_UBER_BADGE),
+ makeAttribute(ANDROID_ATTR_DRAWABLE_BOTTOM, DRAWABLE_UBER_BADGE),
+ makeAttribute(ANDROID_ATTR_DRAWABLE_PADDING, ONE_SP)
+ );
+
+ UberButton uberButton = new UberButton(mContext, attributeSet, 0, 0);
+ Drawable[] drawables = uberButton.getCompoundDrawables();
+ assertNotNull(drawables[0]);
+ assertNotNull(drawables[1]);
+ assertNotNull(drawables[2]);
+ assertNotNull(drawables[3]);
+ assertEquals(1, uberButton.getCompoundDrawablePadding());
+ }
+
+ @Test
+ public void onCreate_whenOverallPaddingSet_shouldAddOverallPadding() {
+ AttributeSet attributeSet = makeAttributeSet(
+ makeAttribute(ANDROID_ATTR_PADDING, ONE_SP)
+ );
+ UberButton uberButton = new UberButton(mContext, attributeSet, 0, 0);
+ assertEquals(1, uberButton.getPaddingLeft());
+ assertEquals(1, uberButton.getPaddingTop());
+ assertEquals(1, uberButton.getPaddingRight());
+ assertEquals(1, uberButton.getPaddingBottom());
+ }
+
+ @Test
+ public void onCreate_whenIndividualPaddingsSet_shouldHaveSeparatePaddings() {
+ AttributeSet attributeSet = makeAttributeSet(
+ makeAttribute(ANDROID_ATTR_PADDING_LEFT, ONE_SP),
+ makeAttribute(ANDROID_ATTR_PADDING_TOP, TWO_SP),
+ makeAttribute(ANDROID_ATTR_PADDING_RIGHT, THREE_SP),
+ makeAttribute(ANDROID_ATTR_PADDING_BOTTOM, FOUR_SP)
+ );
+ UberButton uberButton = new UberButton(mContext, attributeSet, 0, 0);
+ assertEquals(1, uberButton.getPaddingLeft());
+ assertEquals(2, uberButton.getPaddingTop());
+ assertEquals(3, uberButton.getPaddingRight());
+ assertEquals(4, uberButton.getPaddingBottom());
+ }
+
+ @Test
+ public void onCreate_whenIndividualAndOverallPaddingsSet_shouldHaveIndividualPaddingsTrumpOverall() {
+ AttributeSet attributeSet = makeAttributeSet(
+ makeAttribute(ANDROID_ATTR_PADDING, ONE_SP),
+ makeAttribute(ANDROID_ATTR_PADDING_TOP, TWO_SP),
+ makeAttribute(ANDROID_ATTR_PADDING_BOTTOM, FOUR_SP)
+ );
+ UberButton uberButton = new UberButton(mContext, attributeSet, 0, 0);
+ assertEquals(1, uberButton.getPaddingLeft());
+ assertEquals(2, uberButton.getPaddingTop());
+ assertEquals(1, uberButton.getPaddingRight());
+ assertEquals(4, uberButton.getPaddingBottom());
+ }
+
+ @Test
+ public void onCreate_whenTextAttributesSet_shouldAddAllAttributes() {
+ AttributeSet attributeSet = makeAttributeSet(
+ makeAttribute(ANDROID_ATTR_TEXT_COLOR, ANDROID_COLOR_BLACK),
+ makeAttribute(ANDROID_ATTR_GRAVITY, GRAVITY_END),
+ makeAttribute(ANDROID_ATTR_TEXT_SIZE, FOUR_SP),
+ makeAttribute(ANDROID_ATTR_TEXT_STYLE, STYLE_ITALIC),
+ makeAttribute(ANDROID_ATTR_TEXT, TEXT)
+ );
+ UberButton uberButton = new UberButton(mContext, attributeSet, 0, 0);
+ assertEquals(Color.BLACK, uberButton.getCurrentTextColor());
+ assertEquals(Typeface.ITALIC, uberButton.getTypeface().getStyle());
+ assertEquals(4, uberButton.getTextSize(), 0);
+ assertEquals(TEXT, uberButton.getText().toString());
+ assertTrue(uberButton.getGravity() != 0);
+ }
+
+ @Test
+ public void onCreate_whenNoAttributesSet_shouldUseUberButtonDefaults() {
+ UberButton uberButton = new UberButton(RuntimeEnvironment.application, null, 0, 0);
+ Resources resources = mContext.getResources();
+
+ assertEquals(resources.getDrawable(R.drawable.uber_button_background_selector_black),
+ uberButton.getBackground());
+
+ assertNull(uberButton.getCompoundDrawables()[0]);
+ assertNull(uberButton.getCompoundDrawables()[1]);
+ assertNull(uberButton.getCompoundDrawables()[2]);
+ assertNull(uberButton.getCompoundDrawables()[3]);
+
+ assertEquals(0, uberButton.getCompoundDrawablePadding());
+
+ float padding = resources.getDimension(R.dimen.button_padding);
+ assertEquals(padding, uberButton.getPaddingLeft(), 0);
+ assertEquals(padding, uberButton.getPaddingTop(), 0);
+ assertEquals(padding, uberButton.getPaddingRight(), 0);
+ assertEquals(padding, uberButton.getPaddingBottom(), 0);
+
+ assertEquals(resources.getColor(R.color.uber_white_100), uberButton.getCurrentTextColor());
+ assertEquals(Typeface.NORMAL, uberButton.getTypeface().getStyle());
+ assertEquals(resources.getDimension(R.dimen.text_size), uberButton.getTextSize(), 0);
+ assertTrue(uberButton.getGravity() != 0);
+ assertTrue(StringUtils.isEmpty(uberButton.getText().toString()));
+ }
+
+ private static AttributeSet makeAttributeSet(Attribute... attributes) {
+ return new RoboAttributeSet(Arrays.asList(attributes), new CoreShadowsAdapter().getResourceLoader());
+ }
+
+ private static Attribute makeAttribute(String fullyQualifiedAttributeName, Object value) {
+ return new Attribute(fullyQualifiedAttributeName, String.valueOf(value), UBER_PACKAGE_NAME);
+ }
+}
diff --git a/sdk/src/test/resources/deeplinkuris/dropoff_client_and_product_provided b/sdk/src/test/resources/deeplinkuris/dropoff_client_and_product_provided
new file mode 100644
index 00000000..1c0e84b6
--- /dev/null
+++ b/sdk/src/test/resources/deeplinkuris/dropoff_client_and_product_provided
@@ -0,0 +1 @@
+uber:?action=setPickup&client_id=clientId&product_id=productId&pickup=my_location&dropoff%5Blatitude%5D=32.5678&dropoff%5Blongitude%5D=-122.6789&dropoff%5Bnickname%5D=pickupNick&dropoff%5Bformatted_address%5D=Dropoff%20Address
diff --git a/sdk/src/test/resources/deeplinkuris/full_details_uri b/sdk/src/test/resources/deeplinkuris/full_details_uri
new file mode 100644
index 00000000..3ee2554c
--- /dev/null
+++ b/sdk/src/test/resources/deeplinkuris/full_details_uri
@@ -0,0 +1 @@
+uber:?action=setPickup&client_id=clientId&product_id=productId&pickup%5Blatitude%5D=32.1234&pickup%5Blongitude%5D=-122.3456&pickup%5Bnickname%5D=pickupNick&pickup%5Bformatted_address%5D=Pickup%20Address&dropoff%5Blatitude%5D=32.5678&dropoff%5Blongitude%5D=-122.6789&dropoff%5Bnickname%5D=pickupNick&dropoff%5Bformatted_address%5D=Dropoff%20Address
diff --git a/sdk/src/test/resources/deeplinkuris/just_client_provided b/sdk/src/test/resources/deeplinkuris/just_client_provided
new file mode 100644
index 00000000..d59d917b
--- /dev/null
+++ b/sdk/src/test/resources/deeplinkuris/just_client_provided
@@ -0,0 +1 @@
+uber:?action=setPickup&client_id=clientId&pickup=my_location
diff --git a/sdk/src/test/resources/deeplinkuris/no_app_installed b/sdk/src/test/resources/deeplinkuris/no_app_installed
new file mode 100644
index 00000000..ebe53eba
--- /dev/null
+++ b/sdk/src/test/resources/deeplinkuris/no_app_installed
@@ -0,0 +1 @@
+https://m.uber.com/sign-up?client_id=clientId
diff --git a/sdk/src/test/resources/deeplinkuris/no_nickname_or_Address b/sdk/src/test/resources/deeplinkuris/no_nickname_or_Address
new file mode 100644
index 00000000..d8250a17
--- /dev/null
+++ b/sdk/src/test/resources/deeplinkuris/no_nickname_or_Address
@@ -0,0 +1 @@
+uber:?action=setPickup&client_id=clientId&product_id=productId&pickup%5Blatitude%5D=32.1234&pickup%5Blongitude%5D=-122.3456&dropoff%5Blatitude%5D=32.5678&dropoff%5Blongitude%5D=-122.6789
diff --git a/sdk/src/test/resources/deeplinkuris/pickup_and_client_provided b/sdk/src/test/resources/deeplinkuris/pickup_and_client_provided
new file mode 100644
index 00000000..1b953677
--- /dev/null
+++ b/sdk/src/test/resources/deeplinkuris/pickup_and_client_provided
@@ -0,0 +1 @@
+uber:?action=setPickup&client_id=clientId&pickup%5Blatitude%5D=32.1234&pickup%5Blongitude%5D=-122.3456&pickup%5Bnickname%5D=pickupNick&pickup%5Bformatted_address%5D=Pickup%20Address
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..69b6ac4c
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,3 @@
+include ':sdk'
+include ':samples'
+include ':samples:request-button-sample'